# Welcome to the Unigox API documentation

Unigox makes it easy to offer buy & sell crypto in your app or website with just a few lines of code.

Unigox provides partner-ready APIs for fiat-to-crypto and crypto-to-fiat flows across payment rails that are often difficult to cover with traditional providers.

With support for 40+ local payment networks across Africa, Latin America, Asia, and beyond, we enable partners to give their users the ability to buy or sell crypto instantly, without the high fees and restrictions of card processors.

Use these docs to understand the integration model, work through implementation guides, and test live API endpoints directly from the reference.

### Jump right in

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="image">Cover image</th></tr></thead><tbody><tr><td><a href="/pages/32N1XRKkJomEPQ058Pg0">Tools &#x26; SDKs</a></td><td>Get the implementation resources you need, including SDKs, Postman assets, and integration tooling.</td><td><a href="/files/lP1MQKqWuxh4qCXTcSYi">/files/lP1MQKqWuxh4qCXTcSYi</a></td></tr><tr><td><a href="/pages/en2qnWeqZFmACv1sxwPO">Basics</a></td><td>Understand how Unigox works behind the scenes, including webhooks, order states, and production readiness.</td><td><a href="/files/2fOtqty2i560kKcpxufD">/files/2fOtqty2i560kKcpxufD</a></td></tr><tr><td><a href="/pages/H9OCb5rGBAerggPnIf7d">Tutorials</a></td><td>Follow practical integration walkthroughs, including the full partner playbook from setup to monitoring.</td><td><a href="/files/JFoF8CuwBLDJsjHxbHfy">/files/JFoF8CuwBLDJsjHxbHfy</a></td></tr><tr><td><a href="/pages/hMXcsbnBlh4ONwQCbcN2">Drop-in Widget</a></td><td>Launch crypto buy and sell flows in minutes with a hosted, brandable widget with 40+ fiat corridors supported, no backend code required.</td><td><a href="/files/z1mPlEddLKObb7OlMaNl">/files/z1mPlEddLKObb7OlMaNl</a></td></tr><tr><td><a href="/pages/Zhr5KUjj4nfHVqEiWoAJ">API Reference</a></td><td>Browse every Unigox endpoint with authentication details, parameters, schemas, and live request testing in one place.</td><td><a href="/files/6PKAY3PjPNGeoB2ql7d9">/files/6PKAY3PjPNGeoB2ql7d9</a></td></tr><tr><td><a href="/pages/A9vRxDs4iNuIsPxayPiP">Need help?</a></td><td>Find support contacts, escalation paths, and the best way to get help during onboarding or production use.</td><td><a href="/files/Y1C6ANTlb6cF1T3adfQ7">/files/Y1C6ANTlb6cF1T3adfQ7</a></td></tr></tbody></table>

## Where to go next

* Read [Basics](/basics/how-it-works) for webhook behavior, lifecycle, and production readiness.
* Read [Off-Ramp Partner Playbook](/tutorials/off-ramp-playbook) for the full phased integration flow.
* Use [API Reference](broken://pages/Zhr5KUjj4nfHVqEiWoAJ) when implementing requests.


# Tools: Postman & SDKs

This page collects the practical integration assets you would usually need first.

## Postman

[Postman](https://www.postman.com/) is a powerful tool that lets you interact with the Unigox API in a visual, no-code environment. You can send requests, tweak parameters, set headers, manage authentication, and inspect responses—all without writing a single line of code.

It’s perfect for:

* Exploring how endpoints work
* Testing different use cases before coding
* Debugging your integration
* Automating API tests

Find our postman collection here:

{% embed url="<https://github.com/Unigox/postman>" %}

* [Staging Environment](https://github.com/Unigox/postman/blob/main/environments/unigox-staging.postman_environment.json)
* [Production Environment](https://github.com/Unigox/postman/blob/main/environments/unigox-production.postman_environment.json)

With our official Postman collection, you’ll hit the ground running—no setup headaches, just straight into experimenting with the API.

## SDKs

Here you can find our SDK code and start building:

{% embed url="<https://github.com/Unigox/sdk_python>" %}

{% embed url="<https://github.com/Unigox/sdk_javascript>" %}

{% embed url="<https://github.com/Unigox/sdk_csharp>" %}

The SDK helpers are designed to simplify the crypto transfer authorization step during off-ramp execution.


# Why integrate & Revenue Share

### Why Integrate Unigox? <a href="#why-integrate-unigox" id="why-integrate-unigox"></a>

* Access unserved markets no card-based onramp covers
* Unlock new geographies for your users instantly
* Add buy & sell crypto in days, not months
* Earn passive revenue from every user transaction
* Expand with new payment methods on request

### Revenue Share Program <a href="#revenue-share-program" id="revenue-share-program"></a>

As a partner, you earn 20% of all escrow fees generated by trades from happening through your integration.

Unigox charges only 0.5% per trade to the seller, your payout is 0.1% of total trade volume.

Example:

* User trades $1,000 worth of crypto
* Escrow fee = $5 (0.5%)
* Your share = $1 (20% of $5)
* Paid in USDT or USDC directly to your Unigox account web3 wallet


# How it works

This section explains the runtime behavior around the API, especially the parts you rely on operationally: webhooks, order progression, and production readiness.

## How the Unigox flow works

For off-ramp, the integration has four broad stages:

1. One-time setup
2. Your user onboarding
3. Per-order execution
4. Monitoring and reconciliation

The API reference pages provide payload definitions. This page explains how those pieces behave together.

## Order Lifecycle

The normal off-ramp lifecycle is:

1. Quote
2. Initiate offramp
3. Authorize your crypto transfer
4. Wait for payment proof and review from counterparty
5. You confirm that fiat was received
6. Completed

Important status checkpoints:

* `awaiting_crypto_transfer_authorization`
* `crypto_received`
* `fiat_payment_started`
* `fiat_payment_review_started`
* `awaiting_fiat_received_confirmation`
* `completed`

Alternative terminal paths can include:

* `cancelled`
* `failed`
* `dispute_started`

For the actual order actions and state fields, see [Orders](broken://pages/EL7TgfuQ3LD07Z2gtOph).

## Moving to production

Before switching to production:

1. Verify webhook signatures end-to-end.
2. Confirm retries and idempotency behave correctly.
3. Run the happy path and at least one failure or cancellation path.
4. Make sure your reconciliation logic can fall back to polling when needed.


# Get onboarded

1. Contact our team with one of the options
   1. contact our team on the homepage
   2. email our support [support+integration@unigox.com](mailto:support+api@unigox.com)
   3. set up a meeting with our team through [Calendly](https://calendly.com/skyzer-unigox/30min)
2. Submit KYB documentation
3. Receive approval to become a partner


# Authentication

All authenticated requests require the credentials and headers issued for your partner account. Keep staging and production credentials separate.

Before sending live traffic:

1. Confirm your base URL.
2. Confirm your API credentials.
3. Confirm your webhook endpoint is reachable and verified.
4. Confirm your system can handle retries and out-of-order delivery safely.

## Environments

Unigox provides two environments:

* Staging: `https://api-staging.unigox.com`
* Production: `https://api.unigox.com`

Use staging for integration testing and production only after your configuration, webhook handling, and operational processes are verified.


# Webhooks (Callbacks)

## Webhook Delivery

Webhooks are the primary source of order state updates.

Delivery model:

* event type: `order.status.changed`
* transport: HTTPS `POST`
* success condition: any `2xx` response
* signature headers:
  * `X-Unigox-Signature`
  * `X-Unigox-Timestamp`
* user agent: `unigox-webhooks/1.0`

Signature model:

* HMAC-SHA256 over `"<timestamp>.<raw_body>"`
* header format: `sha256=<hex>`
* secret source: your partner `webhook_secret`

Operational behavior from the live dispatcher:

* maximum attempts: `10`
* retry schedule: `1 min`, `5 min`, `15 min`, `1 h`, `6 h`, `12 h`, `24 h`
* once the schedule is exhausted, remaining retries continue at the final `24 h` interval until max attempts is reached
* stale `in_progress` deliveries are recovered back to `pending` after `5 minutes`

Delivery safeguards:

* HTTPS only
* redirects are not followed
* connect/TLS timeout: `3 seconds`
* total request timeout: `10 seconds`
* SSRF protection rejects loopback, private, link-local, and unspecified IPs

Partner guidance:

* de-duplicate by `event_id`
* keep webhook handlers idempotent
* acknowledge quickly, then process asynchronously
* use polling only for recovery and reconciliation

For full webhook request, test, and payload details, see [Webhooks](broken://pages/WlYs2o4Vk0Bunfg6TWmE).


# Deep Link (Hosted Mode)

Redirect users to our hosted widget

*coming soon*


# Embed Widget

Embed the widget directly into your platform

Embed the Unigox buy/sell flow into your site with a single `<script>` tag. The loader creates a cross-origin `<iframe>` pointing at `https://unigox.com/embed`, wires up the required browser permissions, and exposes callbacks for trade and auth events.

> **Live playground:** [widgets.unigox.app](https://widgets.unigox.app)
>
> Use it to try every option below in your browser and copy the generated snippet straight into your page.

***

{% embed url="<https://youtu.be/mdtG4-LW5yA>" %}
Demo video
{% endembed %}

### Quickstart

```html
<div id="unigox-widget"></div>
<script src="https://unigox.com/widget.js"></script>
<script>
  const widget = UnigoxWidget.init({
    container: "#unigox-widget",
    crypto: "USDT",
    fiat: "USD",
    amount: 100,
    onTradeCompleted: function (e) {
      console.log("trade", e.tradeId);
    },
  });
</script>
```

That's it. The loader injects the iframe with the right `sandbox` and `allow` attributes — you do **not** need to construct the iframe yourself.

No registration, partner agreement or API key is required to embed the widget. All options below are optional unless marked otherwise.

> **Need a starting point for your stack?** Copy one of the [`examples`](https://github.com/Unigox/widgets/tree/main/examples) folders — vanilla HTML, React, or WordPress — and tweak from there. Each example mirrors this guide and stays in sync with releases.

***

### Earn referrals from your traffic

If you have a Unigox account and want every signup that goes through the widget on your site to count as your referral, just pass your Unigox username as `ref`:

<pre class="language-html"><code class="lang-html">&#x3C;div id="unigox-widget">&#x3C;/div>
&#x3C;script src="https://unigox.com/widget.js">&#x3C;/script>
&#x3C;script>
  UnigoxWidget.init({
    container: "#unigox-widget",
<strong>    ref: "your-unigox-username",
</strong>  });
&#x3C;/script>
</code></pre>

That's the entire integration. Anyone who signs up while the widget is loaded on your page is attributed to your account — same mechanism as the `unigox.com/?ref=…` link, but built into the widget so you can earn off the buy flow directly on your site.

***

### Init options

Pass these to `UnigoxWidget.init(options)`.

<table><thead><tr><th width="159.9765625">Option</th><th width="134.76171875">Type</th><th width="115.2578125">Required</th><th width="99.51953125">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>container</code></td><td><code>string | HTMLElement</code></td><td>yes</td><td>—</td><td>CSS selector or DOM element. The iframe is appended to it.</td></tr><tr><td><code>partner</code></td><td><code>string</code></td><td>no</td><td>—</td><td>Free-form attribution identifier — any string you want to see in your reports. Not validated. Optional.</td></tr><tr><td><code>ref</code></td><td><code>string</code></td><td>no</td><td>—</td><td>Your Unigox username. New signups initiated inside the widget are credited to this user (referral). Use this for no-integration deployments — paste your username and earn referrals from anyone who signs up via the widget on your site.</td></tr><tr><td><code>type</code></td><td><code>"buy" | "sell" | "buy-sell" | "buy-with-sendout"</code></td><td>no</td><td><code>"buy-sell"</code></td><td>Which side opens first. <code>"buy-sell"</code> shows the full buy/sell toggle. <code>"buy-with-sendout"</code> adds an automatic sendout step — see below. <code>"both"</code> is still accepted as an alias for <code>"buy-sell"</code> for back-compat.</td></tr><tr><td><code>sendoutAddress</code></td><td><code>string</code></td><td>conditional</td><td>—</td><td><strong>Required when <code>type=buy-with-sendout</code>.</strong> Destination address the purchased crypto is sent to after the trade (EVM <code>0x…</code> or Solana base58).</td></tr><tr><td><code>sendoutNetwork</code></td><td><code>string</code></td><td>conditional</td><td>—</td><td><strong>Required when <code>type=buy-with-sendout</code>.</strong> Destination network as a blockchain ticker or name. Accepted values: <code>"ethereum"</code> / <code>"eth"</code>, <code>"optimism"</code> / <code>"op"</code>, <code>"polygon"</code> / <code>"pol"</code>, <code>"unichain"</code> / <code>"uni"</code>, <code>"base"</code>, <code>"arbitrum"</code> / <code>"arb"</code>, <code>"avalanche"</code> / <code>"avax"</code>, <code>"hyperevm"</code> / <code>"hype"</code>, <code>"solana"</code> / <code>"sol"</code>. Numeric chain ids are not accepted.</td></tr><tr><td><code>crypto</code></td><td><code>string</code></td><td>no</td><td>auto</td><td>Pre-selected crypto ticker (<code>"USDT"</code>, <code>"USDC"</code>  are available). Falls back to USDT or the user's balance.</td></tr><tr><td><code>fiat</code></td><td><code>string</code></td><td>no</td><td>auto</td><td>Pre-selected fiat code (e.g. <code>"USD"</code>, <code>"EUR"</code>, <code>"VND"</code>). Falls back to the user's country currency.</td></tr><tr><td><code>amount</code></td><td><code>number</code></td><td>no</td><td>—</td><td>Pre-filled amount. For <code>type=buy</code> this is the <strong>fiat</strong> side ("You spend"); for <code>type=sell</code> it is the <strong>crypto</strong> side.</td></tr><tr><td><code>email</code></td><td><code>string</code></td><td>no</td><td>—</td><td>Prefills the login email. Combined with <code>requireLogin</code> it drives the auto-login flow.</td></tr><tr><td><code>theme</code></td><td><code>"light" | "dark"</code></td><td>no</td><td><code>"light"</code></td><td>Visual theme.</td></tr><tr><td><code>language</code></td><td><code>"en" | "es"</code></td><td>no</td><td><code>"en"</code></td><td>UI language. Unsupported values fall back to <code>"en"</code>. More locales arrive as they are translated.</td></tr><tr><td><code>loginMethods</code></td><td><code>"email" | "web3" | "ton" | "all"</code> or multiple options</td><td>no</td><td><code>"all"</code></td><td>Which login options to show. Pass a single value, <code>"all"</code> for all three, or multiple options separated by commas: <code>"email,web3"</code>, <code>"email,ton"</code>, <code>"web3,ton"</code>.</td></tr><tr><td><code>requireLogin</code></td><td><code>boolean</code></td><td>no</td><td><code>false</code></td><td>If <code>true</code>, force the login screen before the widget opens even when anonymous trading would otherwise be possible.</td></tr><tr><td><code>applyAttribution</code></td><td><code>boolean</code></td><td>no</td><td><code>true</code></td><td>Toggles the "Powered by Unigox" footer inside the widget.</td></tr><tr><td><code>width</code></td><td><code>string</code></td><td>no</td><td><code>"100%"</code></td><td>Iframe width (any CSS length).</td></tr><tr><td><code>height</code></td><td><code>string</code></td><td>no</td><td><code>"700px"</code></td><td>Iframe height. When <strong>omitted</strong>, the widget auto-resizes to fit its content.</td></tr></tbody></table>

#### Callbacks

> **All callbacks are informational, not authoritative.** They are fired from the iframe via `window.postMessage` — anything running in the top-level page can spoof or replay them. Use callbacks for UX (loading state, redirect after success, fire your own analytics) and for non-financial logging. **Do not** use them as proof-of-state for business decisions like releasing a product, crediting an account, or paying out funds. For those, verify via the Unigox API (server-to-server) or, when applicable, via independent on-chain confirmation.
>
> Server-signed webhooks are on the roadmap and will be the authoritative channel; until they ship, treat every event below as a hint, not a fact.

<table><thead><tr><th width="187.76171875">Callback</th><th width="221.3515625">Payload</th><th width="215.328125">Fires when</th><th>Verifiable?</th></tr></thead><tbody><tr><td><code>onReady</code></td><td>—</td><td>The widget finished its initial render.</td><td>UX-only</td></tr><tr><td><code>onAuthChange</code></td><td><code>{ isAuthenticated }</code></td><td>The user signs in or signs out.</td><td>UX-only</td></tr><tr><td><code>onTradeStarted</code></td><td><code>{ tradeId, tradeType }</code></td><td>The user confirmed a trade.</td><td>Verify via API</td></tr><tr><td><code>onTradeCompleted</code></td><td><code>{ tradeId }</code></td><td>A trade reached a terminal success.</td><td>Verify via API</td></tr><tr><td><code>onSendoutStarted</code></td><td><code>{ tradeId, address, chainId }</code></td><td><em>(buy-with-sendout only)</em> The bridge to the partner address was submitted.</td><td>Verify via API</td></tr><tr><td><code>onSendoutCompleted</code></td><td><code>{ tradeId, address, chainId, txHash? }</code></td><td><em>(buy-with-sendout only)</em> The bridge confirmed on the destination chain.</td><td><code>txHash</code> independently verifiable on-chain</td></tr><tr><td><code>onSendoutFailed</code></td><td><code>{ tradeId, code, message }</code></td><td><em>(buy-with-sendout only)</em> The sendout step failed. <code>code</code> is one of the <code>SENDOUT_*</code> codes — see Error codes.</td><td>UX-only</td></tr><tr><td><code>onWidgetError</code></td><td><code>{ code, message }</code></td><td>A misconfig error fired before any trade existed (e.g. missing/invalid <code>sendoutAddress</code>). <code>code</code> is one of the <code>WIDGET_*</code> codes.</td><td>UX-only</td></tr></tbody></table>

#### Handle methods

`init` returns a handle for live control:

```js
widget.configure({ crypto: "USDC", type: "sell" }); // update options at runtime
widget.reset();                                     // send user back to the start view
widget.destroy();                                   // remove the iframe + listeners
```

`configure` accepts the same shape as the init URL params (`type`, `crypto`, `fiat`, `amount`, `vendor`).

***

### Host-page headers

> **Skip this section** if your site does not set a `Content-Security-Policy` or a `Permissions-Policy` header. The widget works out of the box on the default browser permissions — these rules only matter if you have already tightened them.

#### One-block paste (strict-CSP sites)

If your site ships **both** a strict CSP and a Permissions-Policy, drop these two response headers on every page that loads the widget:

```
Content-Security-Policy: script-src https://unigox.com; frame-src https://unigox.com
Permissions-Policy: storage-access=(self "https://unigox.com"), publickey-credentials-get=(self "https://unigox.com"), publickey-credentials-create=(self "https://unigox.com")
```

Merge them into your existing directives — do not replace what you already have. Each line below explains what it unlocks and what breaks without it.

#### Content-Security-Policy

| Directive                       | Why it matters                                                                                                                        |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `script-src https://unigox.com` | Allows `widget.js` to execute. Without it the loader silently fails with a CSP-violation error in the console — no iframe is created. |
| `frame-src https://unigox.com`  | Allows the iframe at `unigox.com/embed` to load. Without it the iframe stays blank. (`child-src` on the legacy directive.)            |

Anything else (`connect-src`, `style-src`, `img-src`) does not need a Unigox entry: all those requests originate **inside** the iframe, which has its own document and is not constrained by the host page's CSP.

#### Permissions-Policy

The widget asks for several powerful browser features via the iframe's `allow` attribute (set automatically by `widget.js`). If your top-level page also sets a `Permissions-Policy`, the host's policy wins — you must delegate those features to `unigox.com` for the `allow` attribute to take effect:

```
Permissions-Policy: storage-access=(self "https://unigox.com"),
                    publickey-credentials-get=(self "https://unigox.com"),
                    publickey-credentials-create=(self "https://unigox.com")
```

| Feature                            | What breaks without it                                                                                     |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `storage-access`                   | Ambient session reuse — users have to log in on every visit instead of reusing their `unigox.com` session. |
| `publickey-credentials-get/create` | Passkey (WebAuthn) sign-in stops working.                                                                  |

***

### Buy with sendout

`type="buy-with-sendout"` turns the widget into a **one-shot fiat → crypto → partner-address** flow. The user buys crypto normally, and the widget then automatically opens a sendout step that bridges the funds to a partner-configured external address over our existing bridge relay.

```html
<div id="unigox-widget"></div>
<script src="https://unigox.com/widget.js"></script>
<script>
  UnigoxWidget.init({
    container: "#unigox-widget",
    partner: "acme",
    type: "buy-with-sendout",
    sendoutAddress: "0xAbCdEf0123456789abcdef0123456789AbCdEf01",
    sendoutNetwork: "ethereum", // also "eth", "polygon"/"pol", "optimism"/"op", "unichain"/"uni", "base", "arbitrum"/"arb", "avalanche"/"avax", "hyperevm"/"hype", "solana"/"sol"
    crypto: "USDC",
    fiat: "USD",
    amount: 100,
    onSendoutCompleted: (e) => console.log("sendout ok", e.tradeId, e.txHash),
  });
</script>
```

#### Behaviour

* The buy/sell toggle is hidden — only BUY is available in this mode.
* A persistent banner shows the sendout destination on every pre-sendout screen (`Sendout to 0xAb…cd on Ethereum`).
* After the trade reaches terminal success and the purchased crypto lands in the user's internal Unigox wallet, the widget auto-navigates to the sendout view (\~1.5 s after completion).
* The sendout view shows a locked amount (matching the trade result), the destination, an expandable quote breakdown, and — if the account has it enabled — inline 2FA fields. The user confirms with a single click.
* Bridge progress, fees and final tx hash are shown inline. The host is notified via `onSendoutStarted / onSendoutCompleted / onSendoutFailed`.

#### Requirements & constraints

* `sendoutAddress` must match the chosen network's address format. EVM networks require a `0x…` address that passes EIP-55 checksum validation when mixed-case; Solana requires a base58 address. Malformed addresses render a "Widget misconfigured" screen before any trade is created, so the user cannot proceed.
* `sendoutNetwork` must be a ticker the widget recognises (`ethereum` / `eth`, `optimism` / `op`, `polygon` / `pol`, `unichain` / `uni`, `base`, `arbitrum` / `arb`, `avalanche` / `avax`, `hyperevm` / `hype`, `solana` / `sol`) **and** the resulting chain must be supported by the Unigox bridge for the chosen `crypto`. Unsupported combinations render a "Sendout misconfigured" screen inside the widget. Unknown tickers render a "Missing required `sendoutNetwork` parameter." configuration error.
* Today the bridge supports **USDC** and **USDT** as source assets. If you plan to use other tickers, contact support first.
* The crypto selector inside the widget is **not** filtered — the user can still pick any listed token; misconfigurations surface at the sendout step rather than at selection time.

***

### Error codes

Error events carry a stable `code` field — branch on `code` for business logic, treat `message` as a human-readable hint that may be reworded between releases. Codes never change shape or meaning within the **v1** loader.

#### `WIDGET_*` — `onWidgetError({ code, message })`

These fire **before any trade exists**. Recovery is always a configuration fix on the partner side; the user cannot continue. The widget also displays a "Widget misconfigured" screen so end-users see something coherent.

<table><thead><tr><th width="286.9296875">Code</th><th>When</th><th>Recovery</th></tr></thead><tbody><tr><td><code>WIDGET_MISSING_SENDOUT_ADDRESS</code></td><td><code>type=buy-with-sendout</code> was used without <code>sendoutAddress</code>.</td><td>Re-init with a non-empty <code>sendoutAddress</code>.</td></tr><tr><td><code>WIDGET_MISSING_SENDOUT_NETWORK</code></td><td><code>type=buy-with-sendout</code> was used without <code>sendoutNetwork</code>, or the ticker is unknown.</td><td>Re-init with a recognised ticker (see <code>sendoutNetwork</code>).</td></tr><tr><td><code>WIDGET_INVALID_SENDOUT_ADDRESS</code></td><td><code>sendoutAddress</code> does not match the chosen network's format (bad EIP-55 checksum on EVM, or non-base58 on Solana).</td><td>Re-init with an address valid for the chosen <code>sendoutNetwork</code>.</td></tr></tbody></table>

#### `SENDOUT_*` — `onSendoutFailed({ tradeId, code, message })`

These fire **after a trade exists**. The user can usually retry from inside the widget; the partner is informed so it can react in its own UI / analytics.

<table><thead><tr><th width="215.359375">Code</th><th>When</th><th>Funds moved?</th><th>Retryable</th></tr></thead><tbody><tr><td><code>SENDOUT_NOT_SUPPORTED</code></td><td>The combination of the user's chosen crypto and <code>sendoutNetwork</code> is not on the bridge. The widget shows a "Sendout misconfigured" screen.</td><td>No</td><td>No — partner-side fix only (different <code>sendoutNetwork</code> or restrict <code>crypto</code>).</td></tr><tr><td><code>SENDOUT_QUOTE_FAILED</code></td><td>The bridge quote API rejected the request (no liquidity, network down, etc.).</td><td>No</td><td>Yes — user retries from inside the widget.</td></tr><tr><td><code>SENDOUT_BRIDGE_FAILED</code></td><td>Source-chain transaction submitted but destination did not confirm, or the relay errored mid-flight.</td><td>Maybe — funds may be in flight.</td><td>Yes inside the widget; if it keeps failing, recovery via <a href="https://unigox.com/wallet"><code>unigox.com/wallet</code></a> and contact support.</td></tr><tr><td><code>SENDOUT_UNKNOWN</code></td><td>Unclassified upstream failure. Treat as <code>SENDOUT_BRIDGE_FAILED</code> for retry purposes.</td><td>Maybe</td><td>Yes — same recovery path.</td></tr></tbody></table>

#### Branching example

```js
UnigoxWidget.init({
  container: "#unigox-widget",
  type: "buy-with-sendout",
  sendoutAddress: "0xAbCd…",
  sendoutNetwork: "base",
  onWidgetError: (e) => {
    // Always a partner-side bug. Surface a config-error UI and re-init.
    analytics.track("unigox_widget_error", e);
  },
  onSendoutFailed: ({ tradeId, code, message }) => {
    if (code === "SENDOUT_NOT_SUPPORTED") {
      analytics.track("unigox_sendout_unsupported", { tradeId });
      // The user is stuck — direct them to support or restrict crypto on your side.
    } else if (code === "SENDOUT_BRIDGE_FAILED") {
      // Funds may be on the way. Direct the user to unigox.com/wallet to recover.
      showRecoveryUI(tradeId);
    }
    // SENDOUT_QUOTE_FAILED / SENDOUT_UNKNOWN — let the user retry inside the widget.
  },
});
```

***

### Versioning & compatibility

The loader at `https://unigox.com/widget.js` auto-updates — every page load fetches the latest. We commit to the following stability rules within the **v1** loader:

* **Init options.** No existing option is ever removed or renamed. Accepted types and the required-vs-optional split do not change. New optional options may be added; partners should ignore options they do not recognise.
* **Callbacks.** Existing payload fields are not renamed or removed. New optional fields may be added; partners should ignore unknown fields and not rely on absence.
* **postMessage protocol.** Existing `type` values and existing payload fields are stable. New `type`s and new optional payload fields may appear.
* **Direct `/embed` URL params.** Same rules as init options.

The widget UI (visual design, copy, internal step ordering, error wording) is **not** covered by these rules — it changes continuously. Build your integration on the contract above, not on screen flow or DOM structure.

#### Breaking changes

When a breaking change is unavoidable we ship it as a new loader URL — `https://unigox.com/widget.v2.js` — and keep the previous URL serving the previous major for at least **90 days**. During that window both URLs work and partners migrate by changing the `<script src>` and reading the migration notes in [`CHANGELOG.md`](https://github.com/Unigox/widgets/blob/main/CHANGELOG.md).

We never publish breaking changes by silently flipping `widget.js`.

#### Tracking changes

All partner-visible changes are recorded in `CHANGELOG.md`. Watch the file on GitHub if you want a notification on every release.

***

### Examples

Stack-specific copy/paste examples live in  [`examples`](https://github.com/Unigox/widgets/tree/main/examples) . Each folder is self-contained — no build step beyond what your stack already needs.

| Stack           | What it shows                                                                              |
| --------------- | ------------------------------------------------------------------------------------------ |
| `vanilla-html/` | One HTML file, one `<div>`, one `<script>`. The minimum integration.                       |
| `react/`        | A reusable `UnigoxWidget` wrapper plus a sample page. SSR-safe.                            |
| `wordpress/`    | Snippet for the WordPress "Custom HTML" block, plus notes on caching plugins, AMP and CSP. |

The examples track the v1 loader, so they auto-pick up additive changes without edits. When the contract changes (`v2`), the examples folder is updated alongside.


# API

Check API reference for all endpoints and this document for all the help.


# First successful Off Ramp

## Your first successful off-ramp

Recommended sequence:

1. Check API health.
2. Fetch supported resources and exchange pairs.
3. Create or identify the user.
4. Collect off-ramp quote and payment details.
5. Initiate the order.
6. Authorize the crypto transfer.
7. Wait for webhook updates.
8. Confirm fiat received in your operational flow.


# Off Ramp Playbook

This tutorial follows the full live off-ramp integration from one-time setup to operational monitoring.

When you need payload and schema details, jump into:

* [User Management](broken://pages/ERBokek4PiLus5LluNlm)
* [Off-Ramp](broken://pages/7ea0DTPZFX584mmP45px)
* [Orders](broken://pages/EL7TgfuQ3LD07Z2gtOph)
* [Webhooks](broken://pages/WlYs2o4Vk0Bunfg6TWmE)

## Phase 1: Setup (one-time)

1. Obtain credentials:
   * `X-API-Key`
   * `webhook_secret`
2. Register your webhook URL:
   * `POST /api/v1/partner/webhooks`
3. Verify delivery:
   * `POST /api/v1/partner/webhooks/test`

## Phase 2: User onboarding (per user)

4. Create user:
   * `POST /api/v1/partner/users`
5. Submit KYC:
   * `POST /api/v1/partner/users/{id}/kyc-submissions`
6. Upload documents for `direct_data`:
   * `POST /api/v1/partner/users/{id}/kyc/documents`
7. Check verification status:
   * `GET /api/v1/partner/users/{id}/verification-status`
8. Create payment details:
   * `POST /api/v1/partner/users/{id}/payment-details`

## Phase 3: Off-ramp order lifecycle

| #  | What happens          | Endpoint                                                     | Result                                   |
| -- | --------------------- | ------------------------------------------------------------ | ---------------------------------------- |
| 9  | Optional estimate     | `POST /api/v1/partner/offramp/estimate`                      | Indicative pricing                       |
| 10 | Quote                 | `POST /api/v1/partner/offramp/quote`                         | Quote locked briefly                     |
| 11 | Initiate order        | `POST /api/v1/partner/offramp/initiate`                      | `awaiting_crypto_transfer_authorization` |
| 12 | Authorize transfer    | `POST /api/v1/partner/orders/{id}/authorize-crypto-transfer` | `crypto_received` after confirmation     |
| 13 | Buyer submits proof   | No partner API call                                          | `fiat_payment_started`                   |
| 14 | Admin reviews proof   | No partner API call                                          | `fiat_payment_review_started`            |
| 15 | Proof accepted        | No partner API call                                          | `awaiting_fiat_received_confirmation`    |
| 16 | Confirm fiat received | `POST /api/v1/partner/orders/{id}/confirm-fiat-received`     | `completed`                              |

Alternative endings:

* `cancelled` for cancellation, expiry, or repeated proof failure
* `failed` for escrow or relay errors
* `dispute_started` when a dispute is opened

## Phase 4: Monitoring

17. Poll a single order:

* `GET /api/v1/partner/orders/{order_id}`

18. List orders for reconciliation:

* `GET /api/v1/partner/orders?status=completed&page=1&limit=20`

Use `status`, `timeline`, and `allowed_actions` if polling is needed as a recovery or reconciliation fallback.

Webhooks should remain the primary source of truth.


# First successful On Ramp

## Your first successful on-ramp

A recommended sequence for your first on-ramp integration test.

### Step 1: Check API health

GET /api/v1/health

Verify the trades service is up before starting.

### Step 2: Get supported resources and exchange pairs

GET /api/v1/partner/supported-resources GET /api/v1/partner/exchange-pairs

Find available fiat/crypto pairs and payment methods for your target market.

### Step 3: Create or identify the end-user

POST /api/v1/partner/users

Register the end-user in Unigox. Use the returned public\_uuid in the next step.

### Step 4: Get an on-ramp price estimate (optional)

POST /api/v1/partner/onramp/estimate

Get indicative pricing before committing to a quote. No user or liquidity reservation required.

### Step 5: Get an on-ramp quote

POST /api/v1/partner/onramp/quote

Pass the end-user's public\_uuid, crypto/fiat currency pair, and amount. The quote is valid for \~60 seconds.

### Step 6: Initiate the order

POST /api/v1/partner/onramp/initiate

Pass the quote\_id. The order is created and matched to a vendor. Save the returned order\_id.

### Step 7: Wait for webhook updates

The order progresses automatically:

created → awaiting\_vendor\_escrow\_funding → awaiting\_fiat\_transfer → fiat\_transfer\_pending → completed

At awaiting\_fiat\_transfer — retrieve vendor payment details via GET /api/v1/partner/orders/{order\_id} and forward them to the end-user so they can send fiat.

### Step 8: Confirm payment sent

POST /api/v1/partner/orders/{order\_id}/confirm-payment-sent

Call this once the end-user has initiated the fiat transfer. The order moves to fiat\_transfer\_pending and the vendor is notified.

### Step 9: Done

Wait for the completed webhook. The USDT is now on the master wallet.

Optional: initiate a cross-chain send-out via bridge-authorization-parameters + authorize-bridge.


# On Ramp Playbook

## On Ramp Playbook

The full phased on-ramp integration flow — from one-time setup to operational monitoring.

### Phase 1: Initial configuration

One-time setup before going live.

| Step | Action                                                | Endpoint                           |
| ---- | ----------------------------------------------------- | ---------------------------------- |
| 1    | Obtain your X-API-Key and webhook\_secret from Unigox | —                                  |
| 2    | Register your webhook endpoint                        | POST /api/v1/partner/webhooks      |
| 3    | Validate webhook delivery                             | POST /api/v1/partner/webhooks/test |

### Phase 2: Per-user onboarding

Required once per end-user before they can transact.

| Step | Action                    | Endpoint                                  |
| ---- | ------------------------- | ----------------------------------------- |
| 1    | Create the user           | POST /api/v1/partner/users                |
| 2    | Submit KYC data           | POST /api/v1/partner/users/{id}/kyc       |
| 3    | Upload identity documents | POST /api/v1/partner/users/{id}/documents |
| 4    | Check verification status | GET /api/v1/partner/users/{id}            |

### Phase 3: On-ramp transaction lifecycle

| Step | Action                                                        | Endpoint                                                               |
| ---- | ------------------------------------------------------------- | ---------------------------------------------------------------------- |
| 1    | (Optional) Get price estimate                                 | POST /api/v1/partner/onramp/estimate                                   |
| 2    | Get quote                                                     | POST /api/v1/partner/onramp/quote                                      |
| 3    | Initiate order                                                | POST /api/v1/partner/onramp/initiate                                   |
| 4    | Webhook: awaiting\_vendor\_escrow\_funding                    | —                                                                      |
| 5    | Webhook: awaiting\_fiat\_transfer                             | —                                                                      |
| 6    | Get vendor payment details, forward to end-user               | GET /api/v1/partner/orders/{order\_id}                                 |
| 7    | (payment\_request only) Submit payer details                  | POST /api/v1/partner/orders/{order\_id}/submit-payer-details           |
| 8    | Confirm payment sent                                          | POST /api/v1/partner/orders/{order\_id}/confirm-payment-sent           |
| 9    | Webhook: fiat\_transfer\_pending                              | —                                                                      |
| 10   | Webhook: completed                                            | —                                                                      |
| 11   | (Optional) Get send-out parameters                            | GET /api/v1/partner/orders/{order\_id}/bridge-authorization-parameters |
| 12   | (Optional) Authorize send-out                                 | POST /api/v1/partner/orders/{order\_id}/authorize-bridge               |
| 13   | (Optional) Webhook: send\_out\_pending → send\_out\_completed | —                                                                      |

Terminal states: completed, cancelled, failed, dispute\_started

### Phase 4: Ongoing operations

Webhooks should remain the primary source of truth. Use polling only for recovery or reconciliation.

| Action                 | Endpoint                               |
| ---------------------- | -------------------------------------- |
| Check individual order | GET /api/v1/partner/orders/{order\_id} |
| List all orders        | GET /api/v1/partner/orders             |


# Third-party payouts

Use this flow when a KYC-verified customer sends money to another person or business. The customer is the **sender** and the party receiving money is the **recipient**.

One Partner API user must represent one real sender. Never route multiple real customers through a shared shell user. Unigox evaluates recipient fan-out, payment value and velocity, relationship/purpose coherence, and screening results for every sender.

### Funding: you pre-fund, we debit

Payouts are funded from **your own crypto balance**, not from your customer's.

Your Unigox partner account is itself an account with a wallet. You top that wallet up with crypto in advance, and every payout your customers make draws down that balance: the wallet signs the transfer into each order's escrow. Your customer never sends crypto to you through Unigox — how they pay you, if at all, is outside this API.

The practical consequence: **an empty balance stops every payout**, not just the next one. Keep the wallet funded ahead of demand.

The address is the one on your Unigox wallet page — it does not change, and it is the same address that appears as `sender_address` on any order's transfer authorization parameters. Send only the token and chain shown there; a transfer on another chain cannot fund an order and is not recoverable.

### End-to-end flow

1. Pre-fund your wallet (above). Crypto must be there before you initiate.
2. Create one partner user for the real sender and complete KYC.
3. Register a partner-scoped recipient identity.
4. Add a validated payout destination to the recipient.
5. Request an off-ramp quote with the sender, recipient, destination, relationship, and purpose.
6. Initiate the quote. Unigox creates the order and applies the same compliance controls used by Portal payouts.
7. Wait for a liquidity provider to accept. A new order starts at `awaiting_liquidity_provider` and has no escrow yet, so `transfer-authorization-parameters` answers `409 INVALID_STATUS` ("no liquidity provider has accepted it"). Poll `GET /orders/{order_id}` — or take a webhook — until `next_action` becomes `authorize_crypto_transfer`.
8. Fund the order's escrow from your balance: `GET /api/v1/partner/orders/{order_id}/transfer-authorization-parameters`, sign the returned ForwardRequest, then `POST /api/v1/partner/orders/{order_id}/authorize-crypto-transfer`. `sender_address` is your wallet; `recipient_address` is the escrow deployed for this order.
9. Read the order and compliance state.

Recipient identity and destination values are versioned. The quote freezes the exact execution values it validated. Later edits never alter an existing quote or order.

### 1. Register a recipient

```http
POST /api/v1/partner/recipients
X-API-Key: <api-key>
Content-Type: application/json
```

```json
{
  "recipient_kind": "business",
  "recipient_name": "Shenzhen Example Trading Co Ltd",
  "recipient_native_name": "深圳示例贸易有限公司",
  "recipient_country": "CN",
  "recipient_business_registration_number": "91440300EXAMPLE"
}
```

The recipient belongs to the authenticated partner, not to one sender. Store the returned `recipient.id`.

### 2. Add a payout destination

```http
POST /api/v1/partner/recipients/{recipient_id}/destinations
X-API-Key: <api-key>
Content-Type: application/json
```

```json
{
  "country_code": "CN",
  "currency": "CNY",
  "rail": "cnaps",
  "institution_id": "china-construction-bank",
  "details": {
    "beneficiary_type": "business",
    "bank_name": "China Construction Bank",
    "account_number": "6222021234567890123",
    "company_name": "Shenzhen Example Trading Co Ltd",
    "company_name_native": "深圳示例贸易有限公司",
    "mobile_number": "13800138000"
  }
}
```

`rail` and `institution_id` are partner-facing slugs — take `rail` from `/api/v1/supported/payment-rails` and `institution_id` from `/api/v1/supported/institutions`. Unigox resolves them to the platform's payment network and payment method IDs and stores those, so the route is a real reference rather than a string.

`/api/v1/supported/institutions` is **paginated**: `limit` defaults to `20` and caps at `100` (a larger value is a `400`, not a silent clamp), and `offset` defaults to `0` — read `pagination.total` to know how far to page. On a corridor like CNY/CNAPS, which lists hundreds of banks, do not conclude a bank is missing from the first page: filter with `search` (matches name or slug, case-insensitive partial), `code` (the institution's own bank code), or `institution_id` (exact slug) instead of paging through everything. Only institutions that are live on the queried rail are listed, so an institution absent from the response cannot be paid — creating a destination on it is refused.

#### `details` field names come from the rail

`details` is validated against the selected rail's configuration, and its keys are that rail's own field names — the same ones `/api/v1/supported/payment-rails` advertises for the format you are using. Do not invent generic names such as `account_holder_name`: they are rejected.

**Pick the format from the institution, not from a table.** A rail can carry several formats, and the one your `details` is validated against is decided by the institution you chose:

1. take the institution from `/api/v1/supported/institutions` — each carries an `institution_type`;
2. find the rail format whose `institution_types` contains that type;
3. send that format's `fields`.

Formats also carry `has_liquidity`. A format with `has_liquidity: false` cannot currently be settled in that corridor — do not build against it. On `cnaps`/CNY today the two bank formats are liquid and the `ewallet` format is not: no `mobile-wallets` institution is active on this corridor, so Alipay and WeChat Pay destinations cannot be created. Check the endpoint rather than this sentence.

#### Paying a company: `beneficiary_type`

A format may have a business sibling (`cnaps-bank` ↔ `cnaps-bank_business`) that applies to the **same** institutions but collects a company's details instead of a person's. The sibling is not selected by the institution — it is selected by a reserved routing key inside `details`:

```json
"beneficiary_type": "business"
```

Omit it, or send `"individual"`, and the individual format is used.

Send it whenever you send company fields. Without it, `company_name` is validated against the individual format and rejected with `unknown field 'company_name' is not allowed for this payment network`.

For `rail: "cnaps"`, institutions of type `traditional-banks`:

| Paying    | `beneficiary_type`     | Required `details`                                                                                                              |
| --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| a company | `business`             | `bank_name`, `account_number`, `company_name`, `company_name_native`, `mobile_number`                                           |
| a person  | `individual` (or omit) | `bank_name`, `account_number`, `id_number`, `first_name`, `last_name`, `native_first_name`, `native_last_name`, `mobile_number` |

`mobile_number` must be an 11-digit Chinese mobile number (`13800138000`) — an international prefix such as `+8613800138000` is rejected.

A missing or malformed field returns `400` with `code: "INVALID_REQUEST"`, and the message names the offending field. Store the returned `data.id`.

#### What reads return

Reads mask the values that identify an account or a person, and return the rest in full so you can tell two destinations apart:

* masked to last-4 (`•••• 1234`): `recipient_id_number`, `recipient_business_registration_number`, and inside `details` `account_number`, `bank_account_number`, `iban`, `card_number`, `id_number`, `national_id`, `tax_id`, `mobile_number`, `phone_number`, `msisdn`;
* returned in full: names, `bank_name`, `province`, `branch`, routing codes, `beneficiary_type`, and every identifier we issued (`id`, `payment_method_id`, `payment_network_id`, `created_at`).

`kind` comes back lowercase (`individual` | `business`), matching the `recipient_kind` you send.

### 3. Request a quote

A third-party payout is routed by `recipient_destination_id`, so `payment_details_id` must be **omitted**. Sending both is rejected with `400 INVALID_REQUEST` rather than one silently winning.

```http
POST /api/v1/partner/offramp/quote
X-API-Key: <api-key>
Content-Type: application/json
```

```json
{
  "user_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "sender_id": "550e8400-e29b-41d4-a716-446655440000",
  "recipient_id": "eb83f57f-814e-4da0-84e8-902d1c60204a",
  "recipient_destination_id": "881b2818-90dd-4e80-a77d-6ce67b6b95a7",
  "sender_recipient_relationship": "supplier",
  "purpose_of_payment": "goods_and_services",
  "purpose_details": "Invoice INV-2026-0042",
  "crypto_currency": "USDT",
  "fiat_currency": "CNY",
  "fiat_amount": "5000",
  "rail": "cnaps"
}
```

`crypto_currency` and the amount are not decoration: a pair with no vendor liquidity, or an amount above what the corridor can currently serve, returns `409 NO_OFFERS_AVAILABLE`. Call `/api/v1/partner/liquidity` or `/api/v1/partner/offramp/estimate` first — both are public — instead of discovering the ceiling from a failed quote.

`user_uuid` and `sender_id` must identify the same real, KYC-verified sender. The destination currency must exactly equal `fiat_currency`; this flow does not support cross-currency recipients. A CNY destination receives CNY.

At quote time Unigox verifies tenant ownership, lifecycle, screening, route, currency, sender eligibility, relationship, and purpose. Trades stores an internal immutable snapshot and integrity hash.

### 4. Initiate the payout

Initiate the returned quote through the standard off-ramp initiate endpoint. Unigox revalidates the frozen quote, applies sender-level controls, and sends reviewable payouts to the shared Compliance queue before execution.

The response includes:

* `recipient_context`: partner-visible sender, recipient, destination, relationship, purpose, and the screening status the payout was authorized against;
* `compliance.case_id`: the compliance record for this payout. It is the `quote_id`, so a partner and a Unigox operator refer to the same record;
* `compliance.status`: `authorized` in v1;
* `compliance.risk_decision`: `allow` in v1;
* `compliance.requires_review`: `false` in v1.

The same two blocks are returned by `GET /api/v1/partner/orders/{order_id}` for the life of the order, so you do not have to persist the initiate response to show a payout's compliance state later.

API-created and Portal-created payouts produce the same record through the same path.

### Compliance&#x20;

Controls are enforced BEFORE the order exists, not as a partner-visible review state machine. A payout that fails a control does not come back as a "held" order — the request is rejected, and you act on the error:

<table><thead><tr><th>Error code</th><th width="92.28125">HTTP</th><th>Meaning</th><th>Partner action</th></tr></thead><tbody><tr><td><code>KYC_NOT_CLEARED</code></td><td>422</td><td>The sender's KYC is not cleared for this partner.</td><td>Complete the sender's KYC; do not substitute another user.</td></tr><tr><td><code>THIRD_PARTY_CONTEXT_INVALID</code></td><td>422</td><td>The recipient/destination is not usable: not found for this partner, archived, screening not <code>cleared</code>, incomplete route, or <code>sender_id</code> ≠ <code>user_uuid</code>.</td><td>Read the message; re-check the recipient, or wait for screening.</td></tr><tr><td><code>INVALID_REQUEST</code></td><td>400</td><td>The payload is wrong — a <code>details</code> field the rail does not accept, a missing required third-party field, <code>payment_details_id</code> sent alongside <code>recipient_destination_id</code>, a destination currency that is not <code>fiat_currency</code>, or a currency no third-party-enabled payout route serves (<code>third-party recipient payout is not available for {CURRENCY}</code>). The message names what to fix.</td><td>Fix the request.</td></tr><tr><td><code>RECIPIENT_NOT_FOUND</code></td><td>404</td><td>No recipient with that id belongs to your partner account, or it was archived.</td><td>Re-create the recipient, or use one from <code>GET /api/v1/partner/recipients</code>.</td></tr><tr><td><code>RECIPIENT_SERVICE_UNAVAILABLE</code></td><td>503</td><td>The recipient directory could not be reached. Nothing about your request was wrong.</td><td>Retry with backoff.</td></tr><tr><td><code>NO_OFFERS_AVAILABLE</code></td><td>409</td><td>No vendor can currently serve this corridor and amount.</td><td>Retry later or use a different amount.</td></tr><tr><td><code>THIRD_PARTY_PAYOUT_AGENT_NOT_READY</code></td><td>409</td><td>The deployed payout agent has not confirmed support for per-payment relationship and purpose, so no third-party CNY order may be created. Your quote is untouched and stays valid.</td><td>Do not retry in a loop — this clears on our side, not yours. Contact support if it persists.</td></tr><tr><td><code>RAIL_ROUTE_MISMATCH</code></td><td>400</td><td>The rail you asked for does not match the route the destination resolves to.</td><td>Send the <code>rail</code> the destination was created with, or omit it.</td></tr><tr><td><code>THIRD_PARTY_PAYOUT_UNDER_REVIEW</code></td><td>422</td><td>Returned on initiate. Compliance put this payout in review — someone looks at it on our side.</td><td>Do not retry the same payout; wait for the outcome.</td></tr><tr><td><code>THIRD_PARTY_PAYOUT_DECLINED</code></td><td>422</td><td>Returned on initiate. Compliance refused it outright, with nothing pending: a breached sender limit, or another initiation for the same sender still in flight.</td><td>An identical retry fails identically. Change the payout, or retry the in-flight case after the other one settles.</td></tr></tbody></table>

`THIRD_PARTY_PAYOUT_AGENT_NOT_READY` is a deliberate stop, not a fault: the relationship and purpose carried per payment on this corridor are only mapped correctly by an agent that has confirmed the capability, and an order created before that would be settled against hardcoded values.

Operator review happens on the Unigox side, over the same records, in the compliance queue. Value/velocity and fan-out holds surfaced to partners as order states are not part of v1 — do not build against the states listed in earlier drafts of this page.

### Updates and deletion

* Reuse an active recipient instead of creating duplicates.
* Update identity with `PATCH /api/v1/partner/recipients/{recipient_id}`. This bumps `version` and resets `screening_status` to `pending`, because the identity that was screened is no longer the identity on file. A quote for that recipient returns `THIRD_PARTY_CONTEXT_INVALID` until screening clears again, so do not PATCH immediately before quoting.
* List destinations with `GET /api/v1/partner/recipients/{recipient_id}/destinations`.
* Archive a recipient with `DELETE /api/v1/partner/recipients/{recipient_id}`.
* Existing orders retain their frozen snapshots after an update or archive.

Partner-facing fields use `recipient_*`, `sender_recipient_relationship`, and `purpose_of_payment`. Vendor-specific terminology is translated only inside vendor integrations.


# Need Help

For integration support:

* Email: `support@unigox.com`
* Book a call: `https://calendly.com/skyzer-unigox/30min`

When contacting support, include:

* partner name
* environment (`staging` or `production`)
* order ID if applicable
* webhook `event_id` if the issue is delivery-related
* timestamps and response payloads where relevant


# Welcome

Unigox is a global P2P fiat on/off-ramp gateway for B2B partners. We focus on local rails coverage, fast execution, and simple partner integration.

Key partner value:

* coverage across Africa, LatAm, Asia, Middle East, Europe, and Australia
* support for local rails and institutions relevant in emerging markets
* compliance flows for KYC, verification, and webhook integrity


# Public endpoints (no API key)

A few discovery endpoints are public so you can explore liquidity and indicative pricing without credentials:

* `GET /api/v1/partner/liquidity`
* `POST /api/v1/partner/onramp/estimate`
* `POST /api/v1/partner/offramp/estimate`

Quote, initiate, user, and order endpoints still require `X-API-Key`.


# API Reference

Endpoint groups are ordered for partner integration flow: `Health` → `Supported Resources` → `Exchange Pairs` → `User Management` → `On-Ramp` → `Off-Ramp` → `Orders` → `Webhooks`.


# Health

Service health and status endpoints

## Health check

> Returns the health status of the API gateway

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Health","description":"Service health and status endpoints"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"paths":{"/health":{"get":{"tags":["Health"],"summary":"Health check","description":"Returns the health status of the API gateway","operationId":"healthCheck","responses":{"200":{"description":"Service is healthy","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"service":{"type":"string"},"time":{"type":"string","format":"date-time"}}}}}}}}}}}
```


# Supported Resources

Discover payment supported resources including cryptocurrencies, blockchains, fiat currencies, and rails

## Cryptocurrencies

> Get a list of all supported cryptocurrencies with their blockchains and contract addresses. Useful for discovering available crypto assets for trading.

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Supported Resources","description":"Discover payment supported resources including cryptocurrencies, blockchains, fiat currencies, and rails"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"paths":{"/api/v1/supported/crypto-currencies":{"get":{"tags":["Supported Resources"],"summary":"Cryptocurrencies","description":"Get a list of all supported cryptocurrencies with their blockchains and contract addresses. Useful for discovering available crypto assets for trading.","operationId":"getCryptoCurrencies","responses":{"200":{"description":"Successfully retrieved cryptocurrencies","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CryptoCurrenciesResponse"}}}}}}}},"components":{"schemas":{"CryptoCurrenciesResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CryptoCurrencyCapability"}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"CryptoCurrencyCapability":{"type":"object","properties":{"code":{"type":"string","description":"Cryptocurrency code (e.g., BTC, ETH)"},"name":{"type":"string","description":"Cryptocurrency name"},"contract":{"type":"string","description":"Token contract address (for ERC-20 tokens)"},"decimals":{"type":"integer","format":"int64","description":"Token decimals (number of decimal places)"},"blockchains":{"type":"array","items":{"type":"string"},"description":"List of supported blockchain names/slugs"}}}}}}
```

## Blockchains

> Get a list of all supported blockchains with their associated cryptocurrencies. Useful for discovering which blockchains are available and what tokens they support.

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Supported Resources","description":"Discover payment supported resources including cryptocurrencies, blockchains, fiat currencies, and rails"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"paths":{"/api/v1/supported/blockchains":{"get":{"tags":["Supported Resources"],"summary":"Blockchains","description":"Get a list of all supported blockchains with their associated cryptocurrencies. Useful for discovering which blockchains are available and what tokens they support.","operationId":"getBlockchains","responses":{"200":{"description":"Successfully retrieved blockchains","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlockchainsResponse"}}}}}}}},"components":{"schemas":{"BlockchainsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BlockchainCapability"}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"BlockchainCapability":{"type":"object","properties":{"name":{"type":"string","description":"Blockchain name"},"slug":{"type":"string","description":"Blockchain slug identifier"},"ticker":{"type":"string","description":"Blockchain ticker symbol"},"crypto_currencies":{"type":"array","items":{"$ref":"#/components/schemas/CryptoCurrencyCapability"},"description":"List of cryptocurrencies supported on this blockchain"}}},"CryptoCurrencyCapability":{"type":"object","properties":{"code":{"type":"string","description":"Cryptocurrency code (e.g., BTC, ETH)"},"name":{"type":"string","description":"Cryptocurrency name"},"contract":{"type":"string","description":"Token contract address (for ERC-20 tokens)"},"decimals":{"type":"integer","format":"int64","description":"Token decimals (number of decimal places)"},"blockchains":{"type":"array","items":{"type":"string"},"description":"List of supported blockchain names/slugs"}}}}}}
```

## Fiat currencies

> Get a list of all supported fiat currencies with their onramp/offramp capabilities. Useful for discovering available fiat currencies for onramp and offramp operations.

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Supported Resources","description":"Discover payment supported resources including cryptocurrencies, blockchains, fiat currencies, and rails"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"paths":{"/api/v1/supported/fiat-currencies":{"get":{"tags":["Supported Resources"],"summary":"Fiat currencies","description":"Get a list of all supported fiat currencies with their onramp/offramp capabilities. Useful for discovering available fiat currencies for onramp and offramp operations.","operationId":"getFiatCurrencies","responses":{"200":{"description":"Successfully retrieved fiat currencies","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FiatCurrenciesResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"FiatCurrenciesResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/FiatCurrencyCapability"}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"FiatCurrencyCapability":{"type":"object","properties":{"code":{"type":"string","description":"Fiat currency code (ISO 4217)"},"name":{"type":"string","description":"Fiat currency name"},"institution_id":{"type":"array","items":{"type":"string"},"description":"List of payment method slugs that support this currency (retrieved from active payment method options)"},"countries":{"type":"array","items":{"type":"string"},"description":"List of ISO 2-letter country codes where this currency is supported (retrieved from active payment method options)"},"onramp":{"type":"boolean","description":"Whether onramp (fiat to crypto) is supported. Currently set to true if there are any active payment method options for this currency.\nThis will be refined in future updates based on trade types and explicit direction configuration.\n"},"offramp":{"type":"boolean","description":"Whether offramp (crypto to fiat) is supported. Currently set to true if there are any active payment method options for this currency.\nThis will be refined in future updates based on trade types and explicit direction configuration.\n"}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}}}
```

## Payment rails

> Get payment rail capabilities for a specific country, currency, and direction (onramp/offramp). Returns available payment rails with their required fields and supported currencies.\
> \
> Partner rule:\
> \- if \`institution\_required\` is \`true\`, send \`institution\_id\` chosen from \`/api/v1/supported/institutions\`\
> \- if \`institution\_required\` is \`false\`, institution selection is not needed for the rail itself\
> \
> Current partner API omission support is explicitly available for:\
> \- \`iban-sepa\`\
> \- \`nip-nigeria\`\
> \- \`imps-neft-india\`\
> \
> For \`iban-sepa\` and \`nip-nigeria\`, partners may omit \`institution\_id\` and the backend resolves the generic \`other-bank\` payment method automatically.\
> \
> \`imps-neft-india\` returns \`institution\_required: false\` and has \*\*no\*\* \`institution\_id\` field: the IFSC code encodes both bank and branch and routes the payout on its own. Required fields are \`bank\_name\`, \`ifsc\_code\`, \`account\_number\`, and \`full\_name\`. Sending \`institution\_id\` is accepted but ignored (it resolves to the generic \`imps-or-neft-transfer\` method); the receiving bank comes from \`bank\_name\` + \`ifsc\_code\`.\
> \
> \*\*Multi-format rails:\*\* some rails accept more than one field-set depending on the\
> chosen institution's type, and return a \`formats\` array. For example GHIPSS (Ghana)\
> and EthSwitch (Ethiopia) accept a \*\*mobile-money\*\* format keyed on \`phone\_number\`\
> and a \*\*bank\*\* format keyed on \`account\_number\`. For these, the top-level \`fields\`\
> is only a backward-compatible best-effort single set; use \`formats\` instead:\
> 1\. pick an institution from \`/api/v1/supported/institutions\` (each carries a fine-grained \`institution\_type\`),\
> 2\. find the format whose \`institution\_types\` contains that \`institution\_type\`,\
> 3\. submit that format's \`fields\`.\
> Each format also carries \`has\_liquidity\`, indicating whether a vendor/provider can\
> currently settle it in the corridor (omitted when undetermined).\
> \
> \*\*Individual vs business beneficiary:\*\* a format may also carry \`beneficiary\_type\`\
> (\`individual\` | \`business\`). A business variant applies to the SAME institutions as its\
> individual sibling (it shares the same \`institution\_types\`) but collects a company's\
> details instead of a person's. To pay a company, choose the format whose\
> \`beneficiary\_type\` is \`business\` and, when creating the payment details, submit that\
> format's \`fields\` PLUS \`"beneficiary\_type": "business"\` inside \`details\`. Omitting\
> \`"beneficiary\_type"\` in the request details defaults to the individual variant. Example: China CNAPS (\`cnaps\`) returns a\
> \`cnaps-bank\` (individual) format and a \`cnaps-bank\_business\` format collecting\
> \`company\_name\` + \`company\_name\_native\`; a corporate CNY payout sends the latter's fields\
> with \`"beneficiary\_type": "business"\`.\
> \
> Example multi-format rail (GHIPSS):\
> \`\`\`json\
> {\
> &#x20; "slug": "ghipss",\
> &#x20; "name": "GHIPSS",\
> &#x20; "institution\_required": false,\
> &#x20; "fields": \[ { "key": "account\_number", "required": true }, { "key": "full\_name", "required": false } ],\
> &#x20; "formats": \[\
> &#x20;   {\
> &#x20;     "id": "mobile-money",\
> &#x20;     "name": "Mobile Money",\
> &#x20;     "institution\_types": \["mobile-money", "mobile-wallets"],\
> &#x20;     "has\_liquidity": true,\
> &#x20;     "fields": \[ { "key": "phone\_number", "required": true }, { "key": "full\_name", "required": false } ]\
> &#x20;   },\
> &#x20;   {\
> &#x20;     "id": "banks",\
> &#x20;     "name": "Bank Account",\
> &#x20;     "institution\_types": \["traditional-banks", "digital-banks"],\
> &#x20;     "has\_liquidity": false,\
> &#x20;     "fields": \[ { "key": "account\_number", "required": true }, { "key": "full\_name", "required": false } ]\
> &#x20;   }\
> &#x20; ]\
> }\
> \`\`\`\
> Here a partner paying out to an M-Pesa/TeleBirr-style mobile wallet must use the\
> \`mobile-money\` format (\`phone\_number\`), not \`account\_number\`.<br>

````json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Supported Resources","description":"Discover payment supported resources including cryptocurrencies, blockchains, fiat currencies, and rails"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"paths":{"/api/v1/supported/payment-rails":{"get":{"tags":["Supported Resources"],"summary":"Payment rails","description":"Get payment rail capabilities for a specific country, currency, and direction (onramp/offramp). Returns available payment rails with their required fields and supported currencies.\n\nPartner rule:\n- if `institution_required` is `true`, send `institution_id` chosen from `/api/v1/supported/institutions`\n- if `institution_required` is `false`, institution selection is not needed for the rail itself\n\nCurrent partner API omission support is explicitly available for:\n- `iban-sepa`\n- `nip-nigeria`\n- `imps-neft-india`\n\nFor `iban-sepa` and `nip-nigeria`, partners may omit `institution_id` and the backend resolves the generic `other-bank` payment method automatically.\n\n`imps-neft-india` returns `institution_required: false` and has **no** `institution_id` field: the IFSC code encodes both bank and branch and routes the payout on its own. Required fields are `bank_name`, `ifsc_code`, `account_number`, and `full_name`. Sending `institution_id` is accepted but ignored (it resolves to the generic `imps-or-neft-transfer` method); the receiving bank comes from `bank_name` + `ifsc_code`.\n\n**Multi-format rails:** some rails accept more than one field-set depending on the\nchosen institution's type, and return a `formats` array. For example GHIPSS (Ghana)\nand EthSwitch (Ethiopia) accept a **mobile-money** format keyed on `phone_number`\nand a **bank** format keyed on `account_number`. For these, the top-level `fields`\nis only a backward-compatible best-effort single set; use `formats` instead:\n1. pick an institution from `/api/v1/supported/institutions` (each carries a fine-grained `institution_type`),\n2. find the format whose `institution_types` contains that `institution_type`,\n3. submit that format's `fields`.\nEach format also carries `has_liquidity`, indicating whether a vendor/provider can\ncurrently settle it in the corridor (omitted when undetermined).\n\n**Individual vs business beneficiary:** a format may also carry `beneficiary_type`\n(`individual` | `business`). A business variant applies to the SAME institutions as its\nindividual sibling (it shares the same `institution_types`) but collects a company's\ndetails instead of a person's. To pay a company, choose the format whose\n`beneficiary_type` is `business` and, when creating the payment details, submit that\nformat's `fields` PLUS `\"beneficiary_type\": \"business\"` inside `details`. Omitting\n`\"beneficiary_type\"` in the request details defaults to the individual variant. Example: China CNAPS (`cnaps`) returns a\n`cnaps-bank` (individual) format and a `cnaps-bank_business` format collecting\n`company_name` + `company_name_native`; a corporate CNY payout sends the latter's fields\nwith `\"beneficiary_type\": \"business\"`.\n\nExample multi-format rail (GHIPSS):\n```json\n{\n  \"slug\": \"ghipss\",\n  \"name\": \"GHIPSS\",\n  \"institution_required\": false,\n  \"fields\": [ { \"key\": \"account_number\", \"required\": true }, { \"key\": \"full_name\", \"required\": false } ],\n  \"formats\": [\n    {\n      \"id\": \"mobile-money\",\n      \"name\": \"Mobile Money\",\n      \"institution_types\": [\"mobile-money\", \"mobile-wallets\"],\n      \"has_liquidity\": true,\n      \"fields\": [ { \"key\": \"phone_number\", \"required\": true }, { \"key\": \"full_name\", \"required\": false } ]\n    },\n    {\n      \"id\": \"banks\",\n      \"name\": \"Bank Account\",\n      \"institution_types\": [\"traditional-banks\", \"digital-banks\"],\n      \"has_liquidity\": false,\n      \"fields\": [ { \"key\": \"account_number\", \"required\": true }, { \"key\": \"full_name\", \"required\": false } ]\n    }\n  ]\n}\n```\nHere a partner paying out to an M-Pesa/TeleBirr-style mobile wallet must use the\n`mobile-money` format (`phone_number`), not `account_number`.\n","operationId":"getPaymentRails","parameters":[{"name":"country","in":"query","description":"ISO 2-letter country code (e.g., NG, US, IN)","schema":{"type":"string"}},{"name":"currency","in":"query","description":"Fiat currency code (e.g., NGN, USD, EUR). Either country or currency must be provided.","schema":{"type":"string"}},{"name":"direction","in":"query","required":true,"description":"Transaction direction - either \"onramp\" (fiat to crypto) or \"offramp\" (crypto to fiat)","schema":{"type":"string","enum":["onramp","offramp"]}}],"responses":{"200":{"description":"Successfully retrieved payment rails","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentRailsResponse"}}}},"400":{"description":"Bad request - missing required parameters or invalid values","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"PaymentRailsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","required":["country","direction","rails"],"properties":{"country":{"type":"string","description":"Country code used in the query"},"currency":{"type":"string","description":"Currency code (from query parameter or first rail)"},"direction":{"type":"string","enum":["onramp","offramp"],"description":"Transaction direction"},"rails":{"type":"array","items":{"$ref":"#/components/schemas/Rails"},"description":"List of available payment rails"},"links":{"type":"object","additionalProperties":{"type":"string"},"description":"Related API endpoints"}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"Rails":{"type":"object","properties":{"slug":{"type":"string","description":"Payment rail slug identifier"},"name":{"type":"string","description":"Payment rail name"},"institution_required":{"type":"boolean","description":"Whether institution (bank) selection is required by the rail itself. In the current partner API, omission of `institution_id` is explicitly supported for `iban-sepa`, `nip-nigeria`, and `imps-neft-india` (which returns `false` and is routed by `ifsc_code` instead)."},"institution_lookup_key":{"type":"string","nullable":true,"description":"Lookup key for institution search, e.g., ifsc or sort_code"},"currencies":{"type":"array","items":{"type":"string"},"description":"List of supported fiat currency codes"},"settlement":{"type":"object","nullable":true,"description":"Settlement information","properties":{"speed":{"type":"string","enum":["INSTANT","SAME_DAY","NEXT_DAY","STANDARD"],"description":"Settlement speed"},"typical_minutes":{"type":"integer","description":"Typical settlement time in minutes"},"note":{"type":"string","nullable":true,"description":"Optional note about settlement"}}},"limits":{"type":"object","nullable":true,"description":"Transaction limits","properties":{"min_amount":{"type":"number","nullable":true,"description":"Minimum transaction amount"},"max_amount":{"type":"number","nullable":true,"description":"Maximum transaction amount (null = no limit)"},"max_per_transaction":{"type":"number","nullable":true,"description":"Maximum per transaction (null = no limit)"}}},"fields":{"type":"array","items":{"$ref":"#/components/schemas/PaymentField"},"description":"Required fields for payment details input. For a multi-format rail (see `formats`) this is a single best-effort field set kept for backward compatibility; the authoritative, institution-specific fields are in `formats`.\n"},"formats":{"type":"array","description":"Present only when a rail accepts more than one field-set (e.g. GHIPSS and EthSwitch accept a mobile-money format keyed on `phone_number` and a bank format keyed on `account_number`). The required field set depends on the chosen institution's type, so pick an institution from `/api/v1/supported/institutions`, match its `institution_type` to a format via `institution_types`, and submit that format's `fields`.\n","items":{"$ref":"#/components/schemas/RailFormat"}}}},"PaymentField":{"type":"object","properties":{"key":{"type":"string","description":"Field identifier/key"},"label":{"type":"string","description":"Human-readable field label"},"type":{"type":"string","description":"Field input type (text, number, select, etc.)"},"required":{"type":"boolean","description":"Whether this field is required"},"pattern":{"type":"string","description":"Regex pattern for validation (optional)"},"placeholder":{"type":"string","description":"Placeholder text for input (optional)"},"lookup":{"type":"boolean","description":"Whether field can be used for lookup (optional)"},"options":{"type":"array","description":"Options for select fields (optional)","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"}}}}}},"RailFormat":{"type":"object","properties":{"id":{"type":"string","description":"Format identifier"},"name":{"type":"string","description":"Human-readable format name"},"institution_types":{"type":"array","items":{"type":"string"},"description":"Institution types this format applies to. Match the chosen institution's type to select the format."},"has_liquidity":{"type":"boolean","description":"Whether a vendor/provider can currently settle this format in the corridor. Omitted when liquidity could not be determined (treat as unknown, not \"no\"). This reflects configured liquidity; live availability is confirmed at quote time.\n"},"beneficiary_type":{"type":"string","enum":["individual","business"],"description":"Present only for rails that distinguish an individual vs a business beneficiary. A `business` format shares its individual sibling's `institution_types` but collects a company's details. To use it, submit this format's `fields` AND set `\"beneficiary_type\": \"business\"` inside the create-payment-details `details` object. Omitting `\"beneficiary_type\"` in the request details defaults to the individual variant.\n"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/PaymentField"},"description":"Required fields for payment details input when paying out to an institution of this format's type."}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}}}
````

## Institutions (banks) for a payment rail

> Get a list of financial institutions (banks) available for a specific payment rail. Useful for populating bank-selection dropdowns. Call with just \`rail\` to page through every bank, or pass \`search\`/\`code\` to filter. For \`imps-neft-india\` this is the embedded India bank directory (\~1,510 banks); selecting one is optional, since the bank is identified at payment-detail creation by \`bank\_name\` + \`ifsc\_code\`.\
> \
> Only institutions that are currently live on the queried rail (and, when given, in the queried country/currency) are returned. An institution missing from the response cannot be paid, and creating a payment detail or payout destination on it is refused.

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Supported Resources","description":"Discover payment supported resources including cryptocurrencies, blockchains, fiat currencies, and rails"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"paths":{"/api/v1/supported/institutions":{"get":{"tags":["Supported Resources"],"summary":"Institutions (banks) for a payment rail","description":"Get a list of financial institutions (banks) available for a specific payment rail. Useful for populating bank-selection dropdowns. Call with just `rail` to page through every bank, or pass `search`/`code` to filter. For `imps-neft-india` this is the embedded India bank directory (~1,510 banks); selecting one is optional, since the bank is identified at payment-detail creation by `bank_name` + `ifsc_code`.\n\nOnly institutions that are currently live on the queried rail (and, when given, in the queried country/currency) are returned. An institution missing from the response cannot be paid, and creating a payment detail or payout destination on it is refused.","operationId":"getInstitutions","parameters":[{"name":"rail","in":"query","required":true,"description":"Payment rail slug (e.g., iban-sepa, pix-brazil, cvu-cbu)","schema":{"type":"string"}},{"name":"institution_id","in":"query","description":"Optional institution ID to filter by specific institution","schema":{"type":"string"}},{"name":"country","in":"query","description":"ISO 2-letter country code for filtering","schema":{"type":"string"}},{"name":"currency","in":"query","description":"ISO 3-letter currency code for filtering","schema":{"type":"string"}},{"name":"search","in":"query","description":"Free-text search by institution name","schema":{"type":"string"}},{"name":"code","in":"query","description":"Filter by the institution's bank code (the `code` field of an institution): the 4-letter IFSC bank prefix for `imps-neft-india` (e.g. `HDFC`), the NIBSS code for `nip-nigeria`, etc. Not a full IFSC — use the 4-letter prefix.","schema":{"type":"string"}},{"name":"limit","in":"query","description":"Maximum number of results to return","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"offset","in":"query","description":"Number of results to skip for pagination","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Successfully retrieved institutions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstitutionsResponse"}}}},"400":{"description":"Bad request - invalid rail or other validation errors","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"InstitutionsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","required":["institutions","pagination"],"properties":{"institutions":{"type":"array","items":{"$ref":"#/components/schemas/Institution"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"Institution":{"type":"object","properties":{"id":{"type":"string","description":"Institution identifier (slug) — pass this as `institution_id` when creating payment details"},"payment_method_id":{"type":"integer","format":"int64","description":"Numeric payment method id. Retail clients submit this (with `payment_network_id`) when saving a payment detail or creating a trade. Omitted for the `imps-neft-india` directory, which has no `payment_methods` rows."},"payment_network_id":{"type":"integer","format":"int64","description":"Numeric id of the queried rail (the `rail` query parameter), submitted alongside `payment_method_id`. Omitted for the `imps-neft-india` directory."},"name":{"type":"string","description":"Institution name"},"type":{"type":"string","description":"Broad institution category (e.g., BANK, WALLET)"},"institution_type":{"type":"string","description":"Fine-grained institution type slug (e.g. `mobile-money`, `mobile-wallets`, `traditional-banks`, `digital-banks`). On a multi-format rail, match this to the rail's `formats[].institution_types` to select the right field set."},"code":{"type":"string","description":"Corridor-specific institution code: the 4-letter IFSC bank prefix for `imps-neft-india`, the NIBSS code for `nip-nigeria`, the reference BIC for `iban-sepa`, etc. Omitted when the institution has no code. For SEPA the BIC is a reference identifier and may not be unique per institution — do not rely on it as a routing BIC."},"branch":{"$ref":"#/components/schemas/InstitutionBranch"},"supported_rails":{"type":"array","items":{"type":"string"},"description":"List of payment rail slugs this institution supports"}}},"InstitutionBranch":{"type":"object","nullable":true,"properties":{"code":{"type":"string","description":"Branch code (e.g., IFSC, sort code)"},"name":{"type":"string","description":"Branch name"},"address":{"type":"string","description":"Branch address"}}},"Pagination":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of items"},"limit":{"type":"integer","description":"Items per page"},"offset":{"type":"integer","description":"Current offset"}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}}}
```

## Fiat Corridors

> Get a snapshot of supported fiat corridors with the all-in best rates Unigox would quote at a 500 USDT reference notional, split by direction (\`onramp\` / \`offramp\`) and partner type (\`licensed\` / \`p2p\`).\
> \
> Every \`rate\` is post-fee. \`unigox\_fee\_pct\` discloses the platform fee for the corridor; \`mid\_market\_rate\` is the Open Exchange Rates reference for the same currency.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Supported Resources","description":"Discover payment supported resources including cryptocurrencies, blockchains, fiat currencies, and rails"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"paths":{"/api/v1/supported/corridors":{"get":{"tags":["Supported Resources"],"summary":"Fiat Corridors","description":"Get a snapshot of supported fiat corridors with the all-in best rates Unigox would quote at a 500 USDT reference notional, split by direction (`onramp` / `offramp`) and partner type (`licensed` / `p2p`).\n\nEvery `rate` is post-fee. `unigox_fee_pct` discloses the platform fee for the corridor; `mid_market_rate` is the Open Exchange Rates reference for the same currency.\n","operationId":"getSupportedCorridors","responses":{"200":{"description":"Snapshot retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportedCorridorsResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"schemas":{"SupportedCorridorsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SupportedCorridorsPayload"}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"SupportedCorridorsPayload":{"type":"object","properties":{"notional_usdt":{"type":"number","format":"double"},"generated_at":{"type":"string","format":"date-time"},"currencies":{"type":"array","items":{"$ref":"#/components/schemas/Corridor"}}}},"Corridor":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"mid_market_rate":{"type":"number","format":"double","nullable":true,"description":"Open Exchange Rates reference rate (fiat per USDT) for this currency. Same value applies to both directions."},"mid_market_updated_at":{"type":"string","format":"date-time","nullable":true},"unigox_fee_pct":{"type":"number","format":"double","nullable":true,"description":"Cheapest active Unigox platform fee for this corridor, in percent. Already baked into every `rate` below."},"onramp":{"$ref":"#/components/schemas/CorridorDirectionInfo"},"offramp":{"$ref":"#/components/schemas/CorridorDirectionInfo"}}},"CorridorDirectionInfo":{"type":"object","description":"Per-direction info for one fiat corridor. The whole object is\nomitted when no active offer exists in that direction. Either\n`p2p` or `licensed` (or both) will be present.\n","properties":{"supported":{"type":"boolean","description":"Always `true` when the object is present (the object is omitted otherwise). Reserved for future states where a corridor may be configured but temporarily without liquidity."},"p2p":{"$ref":"#/components/schemas/CorridorRateQuote"},"licensed":{"$ref":"#/components/schemas/CorridorRateQuote"}}},"CorridorRateQuote":{"type":"object","description":"One partner-type variant of a corridor rate (P2P or Licensed).\n","properties":{"rate":{"type":"number","format":"double","nullable":true,"description":"All-in fiat-per-USDT rate (platform fee already applied)."},"min_notional_fiat":{"type":"number","format":"double","nullable":true,"description":"Smallest amount accepted by any active offer in this variant, in the corridor's local currency. Set only when it exceeds the reference notional in dollar terms — i.e. the customer would need to size up to use this variant."}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}}}
```


# Exchange Pairs

Get information about supported trading pairs

## Supported exchange pairs

> Get a list of all supported cryptocurrency and fiat currency exchange pairs. Shows which currency combinations can be traded.

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Exchange Pairs","description":"Get information about supported trading pairs"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"paths":{"/api/v1/supported-exchange-pairs":{"get":{"tags":["Exchange Pairs"],"summary":"Supported exchange pairs","description":"Get a list of all supported cryptocurrency and fiat currency exchange pairs. Shows which currency combinations can be traded.","operationId":"getSupportedPairs","responses":{"200":{"description":"Successfully retrieved supported pairs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportedPairsResponse"}}}}}}}},"components":{"schemas":{"SupportedPairsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SupportedPair"}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"SupportedPair":{"type":"object","properties":{"crypto_currency_code":{"type":"string","description":"Cryptocurrency code (e.g., USDC, BTC)"},"fiat_currency_code":{"type":"string","description":"Fiat currency code (e.g., USD, EUR)"},"type":{"type":"string","enum":["BUY","SELL"],"description":"Trading pair type - BUY (fiat to crypto) or SELL (crypto to fiat)"}}}}}}
```


# User Management

Endpoints to create and manage user accounts

## Verify API key authentication

> Verifies that the provided API key is valid and authentication is working correctly.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}},"paths":{"/api/v1/partner/verify-auth":{"get":{"tags":["User Management"],"summary":"Verify API key authentication","description":"Verifies that the provided API key is valid and authentication is working correctly.\n","operationId":"verifyAuth","responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string"},"authenticated":{"type":"boolean"}}}}}]}}}},"401":{"description":"Unauthorized - invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
```

## Create or get partner user

> Creates a new user relation or returns existing if already created. Returns user\_ref (same as input) for subsequent API calls. Partners can only access their own users.\
> \
> Each partner user must represent one actual payout beneficiary/end customer. Create a separate partner user for each beneficiary. Do not attach payment details for unrelated people to the same \`user\_uuid\`.\
> \
> A partner user is the SENDER. To pay someone else, do not create a shell user for the payee — register a recipient (\`/api/v1/partner/recipients\`) and quote against it. See the Third-party payouts guide.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"CreateUserResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"user_uuid":{"type":"string","format":"uuid","description":"Public UUID of the user. Use this value in all subsequent API calls."},"email":{"type":"string","format":"email"},"kyc_status":{"type":"string","enum":["NOT_INITIATED","IN_PROGRESS","UNDER_REVIEW","VERIFIED","VERIFICATION_REJECTED"]},"created_at":{"type":"string","format":"date-time"}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}}}},"paths":{"/api/v1/partner/users":{"post":{"tags":["User Management"],"summary":"Create or get partner user","description":"Creates a new user relation or returns existing if already created. Returns user_ref (same as input) for subsequent API calls. Partners can only access their own users.\n\nEach partner user must represent one actual payout beneficiary/end customer. Create a separate partner user for each beneficiary. Do not attach payment details for unrelated people to the same `user_uuid`.\n\nA partner user is the SENDER. To pay someone else, do not create a shell user for the payee — register a recipient (`/api/v1/partner/recipients`) and quote against it. See the Third-party payouts guide.\n","operationId":"createPartnerUser","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_ref","email"],"properties":{"user_ref":{"type":"string","description":"Partner's unique user ID (usually primary key or UUID)"},"email":{"type":"string","format":"email","description":"User's email address"}}}}}},"responses":{"200":{"description":"User already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserResponse"}}}},"201":{"description":"Successfully created user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserResponse"}}}},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized (missing or invalid X-API-Key)"},"500":{"description":"Internal Server Error"}}}}}}
```

## Partner user by ID

> Get user details including KYC status and payment profiles using the user\_uuid returned during user creation. Partners can only access their own users.

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"GetUserResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"user_uuid":{"type":"string","format":"uuid","description":"Public UUID of the user. Use this value in all subsequent API calls."},"email":{"type":"string","format":"email"},"created_at":{"type":"string","format":"date-time"},"kyc":{"type":"object","properties":{"status":{"type":"string","enum":["NOT_INITIATED","IN_PROGRESS","UNDER_REVIEW","VERIFIED","VERIFICATION_REJECTED"],"description":"Current partner-facing KYC status. `UNDER_REVIEW` means the customer is under\nmanual compliance review — not yet cleared, and unlike `IN_PROGRESS` (automated,\nusually a couple of minutes) it can take up to 24 hours. The internal review\nstate name is never exposed.\n"},"kyc_method":{"type":"string","enum":["direct_data","external_token","handoff"],"description":"KYC submission method used"},"first_name":{"type":"string","description":"User's first name from KYC data"},"last_name":{"type":"string","description":"User's last name from KYC data"},"country_code":{"type":"string","description":"User's country code (ISO 2-letter) from KYC data"},"rejection_reason":{"type":"string","nullable":true,"description":"Reason for KYC rejection (if status is VERIFICATION_REJECTED)"},"updated_at":{"type":"string","format":"date-time","description":"Last update timestamp for KYC status"}}},"payment_profiles":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"rail":{"type":"string"},"currency":{"type":"string"},"institution_name":{"type":"string","nullable":true},"institution_id":{"type":"string","nullable":true,"description":"Institution identifier (our internal slug)"},"details":{"type":"object","additionalProperties":true}}}}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}}}},"paths":{"/api/v1/partner/users/{user_uuid}":{"get":{"tags":["User Management"],"summary":"Partner user by ID","description":"Get user details including KYC status and payment profiles using the user_uuid returned during user creation. Partners can only access their own users.","operationId":"getPartnerUser","parameters":[{"name":"user_uuid","in":"path","required":true,"description":"The user_uuid returned by POST /partner/users. Partners can only access their own users.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"User found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetUserResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}}}}
```

## Submit KYC data for a partner user

> Submit KYC (Know Your Customer) data for a partner user. Supports three methods:\
> \- direct\_data: Partner submits PII data directly, user is verified\
> \- external\_token: Partner provides external KYC provider credentials (SumSub token or Persona inquiry\_id)\
> \- handoff: Unigox handles KYC verification and generates verification URL\
> \
> \*\*Important for EUR/AUD/GBP off-ramp:\*\* EUR, AUD, and GBP payouts require the user's\
> physical address. Include \`address\`, \`city\`, and \`postal\_code\` in the PII data\
> when submitting KYC. If not provided at submission time, you can add them later with\
> \`PATCH /partner/users/{user\_uuid}/kyc\`. Without address, EUR/AUD/GBP orders will be created\
> with status \`pending\_address\` until the address is provided.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"KYCSubmitDirectData":{"type":"object","required":["method","data"],"properties":{"method":{"type":"string","enum":["direct_data"]},"data":{"type":"object","required":["pii"],"properties":{"pii":{"type":"object","required":["first_name","last_name","country_code"],"properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"country_code":{"type":"string"},"dob":{"type":"string","format":"date","description":"Date of birth (YYYY-MM-DD). Optional, but when provided it must be a real date in the past and indicate an age of at least 18. A future, malformed, or under-18 date is rejected with HTTP 400 (see error responses below)."},"middle_name":{"type":"string"},"phone_number":{"type":"string","description":"E.164 format"},"id_number":{"type":"string","description":"Government identity/registry number. Enables the **NIN/BVN + selfie** verification option: instead of uploading an ID-document image, the customer proves identity with this number plus a live selfie (no ID photo). AiPrise validates the number against the national registry (NIN->NIMC, BVN->NIBSS) and face-matches the selfie against the registry photo. After this submission, upload only a `selfie` (document_type=selfie) to the documents endpoint - verification starts automatically. Supported for the registry number types listed under `id_type`, in the countries those registries cover."},"id_type":{"type":"string","description":"Registry type for `id_number`. For the NIN/BVN + selfie option it must be one of the listed registry types (image-only IDs such as passport/driver licence are NOT accepted - those use the ID-image flow). An unsupported/missing value returns `400 { \"error_key\": \"invalid_identity_number_type\" }`.","enum":["NIN","NIN_SLIP","BVN","NATIONAL_ID","VOTER_ID","TAX_ID","KENYA_KRA_PIN"]},"address":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City. Required for EUR/AUD/GBP offramp (auto-populates into payment details)."},"postal_code":{"type":"string"}}}}}}},"KYCSubmitExternalToken":{"type":"object","required":["method","data"],"properties":{"method":{"type":"string","enum":["external_token"]},"data":{"type":"object","required":["provider"],"description":"Provider-specific fields:\n- SumSub: requires `token` (SumSub share token)\n- Persona: requires `inquiry_id` (Persona inquiry ID, e.g. inq_xxx)\n","properties":{"provider":{"type":"string","enum":["sumsub","persona"],"description":"KYC provider name"},"token":{"type":"string","description":"SumSub share token (required when provider=sumsub)"},"inquiry_id":{"type":"string","description":"Persona inquiry ID (required when provider=persona). This is the inq_xxx identifier returned by Persona when the inquiry was created."}}}}},"KYCSubmitHandoff":{"type":"object","required":["method","data"],"properties":{"method":{"type":"string","enum":["handoff"]},"data":{"type":"object","properties":{"pii":{"type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"country_code":{"type":"string"}}}}}}},"KYCSubmitResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"kyc_status":{"type":"string","enum":["NOT_INITIATED","IN_PROGRESS","UNDER_REVIEW","VERIFIED","VERIFICATION_REJECTED"],"description":"A submission may asynchronously move to `UNDER_REVIEW` (manual compliance review,\nup to 24 hours) via the status endpoint and the `user.kyc.updated` webhook — distinct\nfrom `IN_PROGRESS` (automated, usually a couple of minutes). Treat both as not cleared.\n"},"message":{"type":"string"},"verification_url":{"type":"string","format":"uri","nullable":true,"description":"Verification URL (only returned for handoff method)"}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}},"paths":{"/api/v1/partner/users/{user_uuid}/kyc-submissions":{"post":{"tags":["User Management"],"summary":"Submit KYC data for a partner user","description":"Submit KYC (Know Your Customer) data for a partner user. Supports three methods:\n- direct_data: Partner submits PII data directly, user is verified\n- external_token: Partner provides external KYC provider credentials (SumSub token or Persona inquiry_id)\n- handoff: Unigox handles KYC verification and generates verification URL\n\n**Important for EUR/AUD/GBP off-ramp:** EUR, AUD, and GBP payouts require the user's\nphysical address. Include `address`, `city`, and `postal_code` in the PII data\nwhen submitting KYC. If not provided at submission time, you can add them later with\n`PATCH /partner/users/{user_uuid}/kyc`. Without address, EUR/AUD/GBP orders will be created\nwith status `pending_address` until the address is provided.\n","operationId":"submitKYC","parameters":[{"name":"user_uuid","in":"path","required":true,"description":"The user_uuid returned by POST /partner/users. Partners can only access their own users.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/KYCSubmitDirectData"},{"$ref":"#/components/schemas/KYCSubmitExternalToken"},{"$ref":"#/components/schemas/KYCSubmitHandoff"}]}}}},"responses":{"200":{"description":"KYC submitted successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KYCSubmitResponse"}}}},"400":{"description":"Bad Request — invalid method, missing required data, or a date of birth that fails validation. The response body is `{ \"success\": false, \"data\": { \"error_key\": \"...\", \"message\": \"...\" } }`. Possible `error_key` values include: `underage_not_allowed` — \"User must be at least 18 years old. The provided date_of_birth indicates an age under 18.\"; and `invalid_date_of_birth` — \"date_of_birth is invalid. Provide a real date in YYYY-MM-DD format that is in the past.\" `invalid_identity_number_type` — \"id_type is required for NIN/BVN + selfie verification and must be one of the supported identity number types (e.g. NIN, BVN).\"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"User not found"},"500":{"description":"Internal Server Error"}}}}}}
```

## Upload KYC document for a partner user

> Upload a KYC document for a partner user. Choose one of two upload methods:\
> \
> \---\
> \
> \### Method 1: File Upload\
> Send the document as a file attachment.\
> \`\`\`\
> POST /api/v1/partner/users/{user\_uuid}/kyc/documents\
> Content-Type: multipart/form-data\
> \
> document: \<file>\
> document\_type: identity\_front\
> \`\`\`\
> \
> \### Method 2: Single URL Upload\
> Send a URL pointing to one document image.\
> \`\`\`json\
> {\
> &#x20; "upload\_method": "url\_link",\
> &#x20; "document\_type": "identity\_front",\
> &#x20; "url": "<https://example.com/id-front.jpg"\\>
> }\
> \`\`\`\
> \
> \### Method 3: Batch URL Upload\
> Upload multiple documents in one call (up to 10).\
> \`\`\`json\
> {\
> &#x20; "upload\_method": "url\_link",\
> &#x20; "documents": \[\
> &#x20;   { "document\_type": "identity\_front", "url": "<https://example.com/id-front.jpg>" },\
> &#x20;   { "document\_type": "selfie", "url": "<https://example.com/selfie.jpg>" }\
> &#x20; ]\
> }\
> \`\`\`\
> \
> \*\*URL requirements:\*\* HTTPS only, 10MB per file, JPEG/PNG/GIF/PDF.\
> \
> \---\
> \
> \*\*Supported \`document\_type\` values\*\* — the API accepts exactly the following (this is the\
> complete, finite list; anything else is rejected). \*\*Required:\*\* a \`selfie\` \*\*and\*\* one\
> identity-document front (\`identity\_front\` or any equivalent ID type listed below). All other\
> values are optional (document back sides and alternative ID types).\
> \
> \*\*NIN/BVN + selfie option (verify with a registry number instead of an ID image).\*\* This is a\
> \*\*two-step flow across two endpoints\*\* — the registry number is submitted as \*\*text\*\*, never\
> uploaded as a file:\
> \
> &#x20; 1\. On the \*\*submit-KYC\*\* endpoint (\`POST .../kyc-submissions\`, method \`direct\_data\`), send the\
> &#x20;    customer's data \*\*plus\*\* \`id\_number\` (the registry number, as a string) and \`id\_type\`\
> &#x20;    (e.g. \`NIN\`, \`BVN\`). No image is uploaded in this step.\
> &#x20; 2\. On \*\*this\*\* documents endpoint, upload \*\*only a \`selfie\`\*\* (\`document\_type: selfie\`) — \*\*no\
> &#x20;    ID-document image\*\*. The registry number from step 1 satisfies the identity requirement, so\
> &#x20;    verification starts automatically (national-registry lookup + selfie face-match against the\
> &#x20;    registry photo).\
> \
> The three upload methods below (multipart / single URL / batch URL) are for \*\*image files\*\* only\
> — they are not how the registry number is sent (that is the \`id\_number\` text field in step 1).\
> \
> \`document\_type\` only selects which slot (front / back / selfie) an image occupies. It is \*\*not\*\*\
> used as a document-category and it does \*\*not\*\* set the country: our verification system detects\
> the actual document type from the image, and the country is taken from the customer's KYC profile\
> (their identity/address country), independent of this value. The country names below are naming\
> hints to help you choose — they are not enforced, so a country-specific value is never required.\
> \
> \- Generic identity document — \*\*general\*\* (any country): \`identity\_front\`, \`identity\_back\`\
> \- Passport — \*\*general\*\* (any country): \`passport\`, \`passport\_front\`\
> \- National ID — \*\*general\*\* (any country; covers e.g. Nigeria's NIN card, Kenya national ID,\
> &#x20; India Aadhaar): \`national\_id\`, \`national\_id\_front\`, \`national\_id\_back\`\
> \- Driver license — \*\*general\*\* (any country): \`driver\_license\`, \`driver\_license\_front\`, \`driver\_license\_back\`\
> \- Voter ID — \*\*general\*\* (any country): \`voter\_id\`, \`voter\_id\_front\`, \`voter\_id\_back\`\
> \- Resident card — \*\*general\*\* (any country): \`resident\_card\`, \`resident\_card\_front\`, \`resident\_card\_back\`\
> \- ID card — \*\*general\*\* (any country; catch-all for local cards without their own type below,\
> &#x20; e.g. Indonesia KTP, Pakistan Nadra Card): \`id\_card\`, \`id\_card\_front\`, \`id\_card\_back\`\
> \- Ghana card — \*\*Ghana\*\* (NIA biometric national ID): \`ghana\_card\`, \`ghana\_card\_front\`, \`ghana\_card\_back\`\
> \- Ghana SSNIT card — \*\*Ghana\*\* (social-security card): \`ghana\_ssnit\_card\`, \`ghana\_ssnit\_card\_front\`, \`ghana\_ssnit\_card\_back\`\
> \- Kenya alien card — \*\*Kenya\*\* (non-citizen resident ID): \`kenya\_alien\_card\`, \`kenya\_alien\_card\_front\`, \`kenya\_alien\_card\_back\`\
> \- Green book — \*\*South Africa\*\* (legacy green barcoded ID book): \`green\_book\`, \`green\_book\_front\`, \`green\_book\_back\`\
> \- Tax ID — \*\*general\*\* label (primary example: India PAN card): \`tax\_id\`, \`tax\_id\_front\`, \`tax\_id\_back\`\
> \- Colombia PPT — \*\*Colombia\*\* (Permiso por Protección Temporal): \`colombia\_ppt\`, \`colombia\_ppt\_front\`, \`colombia\_ppt\_back\`\
> \- Selfie — \*\*general\*\* (any country): \`selfie\`\
> \
> Any front-side identity type above (e.g. \`passport\`, \`national\_id\`, \`tax\_id\`) satisfies the ID\
> requirement — not only the literal \`identity\_front\`. \*\*Nigeria's NIN\*\* is submitted as\
> \`national\_id\` (with the customer's country set to Nigeria); there is no separate \`nin\` value.\
> \
> \*\*Automatic Verification:\*\* After an identity document + \`selfie\` + KYC data are present, the\
> service queues verification processing and returns immediately. The identity document can be\
> any accepted ID type above, not only \`identity\_front\` (e.g. \`passport\` + \`selfie\` triggers\
> verification). A \`200\` response means the documents were accepted; it is not a final\
> verification result. Asynchronous verification checks can move the user to\
> \`VERIFICATION\_REJECTED\` after this response.\
> \
> To trigger verification explicitly, or to check exactly what is still missing, call\
> \`POST /api/v1/partner/users/{user\_uuid}/kyc/verify\` (below).\
> \
> Obtain the eventual outcome by polling\
> \`GET /api/v1/partner/users/{user\_uuid}/verification-status\` or consuming the\
> \`user.kyc.updated\` webhook. The upload response does not include a verification session ID\
> or \`kyc\_status\`, because asynchronous state can change immediately after acceptance.<br>

````json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"KycDocumentType":{"type":"string","description":"Complete set of document types accepted by the partner KYC upload endpoint. `document_type` only selects the upload slot (front/back/selfie); it is not a document-category and does not set the country — our verification system detects the document type from the image, and the country comes from the customer's KYC profile. Values named after a country (`ghana_card`, `ghana_ssnit_card`, `kenya_alien_card`, `green_book`, `colombia_ppt`) are country-specific hints; all others (`passport`, `national_id`, `driver_license`, `voter_id`, `resident_card`, `id_card`, `tax_id`, `identity_front`/`identity_back`, `selfie`) are general — any country. Nigeria's NIN is submitted as `national_id`.","enum":["identity_front","identity_back","selfie","passport","passport_front","national_id","national_id_front","national_id_back","driver_license","driver_license_front","driver_license_back","voter_id","voter_id_front","voter_id_back","resident_card","resident_card_front","resident_card_back","id_card","id_card_front","id_card_back","ghana_card","ghana_card_front","ghana_card_back","ghana_ssnit_card","ghana_ssnit_card_front","ghana_ssnit_card_back","kenya_alien_card","kenya_alien_card_front","kenya_alien_card_back","green_book","green_book_front","green_book_back","tax_id","tax_id_front","tax_id_back","colombia_ppt","colombia_ppt_front","colombia_ppt_back"]}}},"paths":{"/api/v1/partner/users/{user_uuid}/kyc/documents":{"post":{"tags":["User Management"],"summary":"Upload KYC document for a partner user","description":"Upload a KYC document for a partner user. Choose one of two upload methods:\n\n---\n\n### Method 1: File Upload\nSend the document as a file attachment.\n```\nPOST /api/v1/partner/users/{user_uuid}/kyc/documents\nContent-Type: multipart/form-data\n\ndocument: <file>\ndocument_type: identity_front\n```\n\n### Method 2: Single URL Upload\nSend a URL pointing to one document image.\n```json\n{\n  \"upload_method\": \"url_link\",\n  \"document_type\": \"identity_front\",\n  \"url\": \"https://example.com/id-front.jpg\"\n}\n```\n\n### Method 3: Batch URL Upload\nUpload multiple documents in one call (up to 10).\n```json\n{\n  \"upload_method\": \"url_link\",\n  \"documents\": [\n    { \"document_type\": \"identity_front\", \"url\": \"https://example.com/id-front.jpg\" },\n    { \"document_type\": \"selfie\", \"url\": \"https://example.com/selfie.jpg\" }\n  ]\n}\n```\n\n**URL requirements:** HTTPS only, 10MB per file, JPEG/PNG/GIF/PDF.\n\n---\n\n**Supported `document_type` values** — the API accepts exactly the following (this is the\ncomplete, finite list; anything else is rejected). **Required:** a `selfie` **and** one\nidentity-document front (`identity_front` or any equivalent ID type listed below). All other\nvalues are optional (document back sides and alternative ID types).\n\n**NIN/BVN + selfie option (verify with a registry number instead of an ID image).** This is a\n**two-step flow across two endpoints** — the registry number is submitted as **text**, never\nuploaded as a file:\n\n  1. On the **submit-KYC** endpoint (`POST .../kyc-submissions`, method `direct_data`), send the\n     customer's data **plus** `id_number` (the registry number, as a string) and `id_type`\n     (e.g. `NIN`, `BVN`). No image is uploaded in this step.\n  2. On **this** documents endpoint, upload **only a `selfie`** (`document_type: selfie`) — **no\n     ID-document image**. The registry number from step 1 satisfies the identity requirement, so\n     verification starts automatically (national-registry lookup + selfie face-match against the\n     registry photo).\n\nThe three upload methods below (multipart / single URL / batch URL) are for **image files** only\n— they are not how the registry number is sent (that is the `id_number` text field in step 1).\n\n`document_type` only selects which slot (front / back / selfie) an image occupies. It is **not**\nused as a document-category and it does **not** set the country: our verification system detects\nthe actual document type from the image, and the country is taken from the customer's KYC profile\n(their identity/address country), independent of this value. The country names below are naming\nhints to help you choose — they are not enforced, so a country-specific value is never required.\n\n- Generic identity document — **general** (any country): `identity_front`, `identity_back`\n- Passport — **general** (any country): `passport`, `passport_front`\n- National ID — **general** (any country; covers e.g. Nigeria's NIN card, Kenya national ID,\n  India Aadhaar): `national_id`, `national_id_front`, `national_id_back`\n- Driver license — **general** (any country): `driver_license`, `driver_license_front`, `driver_license_back`\n- Voter ID — **general** (any country): `voter_id`, `voter_id_front`, `voter_id_back`\n- Resident card — **general** (any country): `resident_card`, `resident_card_front`, `resident_card_back`\n- ID card — **general** (any country; catch-all for local cards without their own type below,\n  e.g. Indonesia KTP, Pakistan Nadra Card): `id_card`, `id_card_front`, `id_card_back`\n- Ghana card — **Ghana** (NIA biometric national ID): `ghana_card`, `ghana_card_front`, `ghana_card_back`\n- Ghana SSNIT card — **Ghana** (social-security card): `ghana_ssnit_card`, `ghana_ssnit_card_front`, `ghana_ssnit_card_back`\n- Kenya alien card — **Kenya** (non-citizen resident ID): `kenya_alien_card`, `kenya_alien_card_front`, `kenya_alien_card_back`\n- Green book — **South Africa** (legacy green barcoded ID book): `green_book`, `green_book_front`, `green_book_back`\n- Tax ID — **general** label (primary example: India PAN card): `tax_id`, `tax_id_front`, `tax_id_back`\n- Colombia PPT — **Colombia** (Permiso por Protección Temporal): `colombia_ppt`, `colombia_ppt_front`, `colombia_ppt_back`\n- Selfie — **general** (any country): `selfie`\n\nAny front-side identity type above (e.g. `passport`, `national_id`, `tax_id`) satisfies the ID\nrequirement — not only the literal `identity_front`. **Nigeria's NIN** is submitted as\n`national_id` (with the customer's country set to Nigeria); there is no separate `nin` value.\n\n**Automatic Verification:** After an identity document + `selfie` + KYC data are present, the\nservice queues verification processing and returns immediately. The identity document can be\nany accepted ID type above, not only `identity_front` (e.g. `passport` + `selfie` triggers\nverification). A `200` response means the documents were accepted; it is not a final\nverification result. Asynchronous verification checks can move the user to\n`VERIFICATION_REJECTED` after this response.\n\nTo trigger verification explicitly, or to check exactly what is still missing, call\n`POST /api/v1/partner/users/{user_uuid}/kyc/verify` (below).\n\nObtain the eventual outcome by polling\n`GET /api/v1/partner/users/{user_uuid}/verification-status` or consuming the\n`user.kyc.updated` webhook. The upload response does not include a verification session ID\nor `kyc_status`, because asynchronous state can change immediately after acceptance.\n","operationId":"uploadPartnerKYCDocument","parameters":[{"name":"user_uuid","in":"path","required":true,"description":"Public UUID of the user","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["document","document_type"],"properties":{"document":{"type":"string","format":"binary","description":"Document file (JPEG, PNG, GIF, or PDF, max 10MB)"},"document_type":{"$ref":"#/components/schemas/KycDocumentType"}}}},"application/json":{"schema":{"oneOf":[{"title":"Single URL Upload","description":"Upload one document by providing an HTTPS URL.","type":"object","required":["upload_method","document_type","url"],"properties":{"upload_method":{"type":"string","enum":["url_link"]},"document_type":{"$ref":"#/components/schemas/KycDocumentType"},"url":{"type":"string","format":"uri","description":"HTTPS URL of the image. 10MB max."}}},{"title":"Batch URL Upload","description":"Upload multiple documents in one call (up to 10).","type":"object","required":["upload_method","documents"],"properties":{"upload_method":{"type":"string","enum":["url_link"]},"documents":{"type":"array","maxItems":10,"items":{"type":"object","required":["document_type","url"],"properties":{"document_type":{"$ref":"#/components/schemas/KycDocumentType"},"url":{"type":"string","format":"uri"}}}}}}]}}}},"responses":{"200":{"description":"Documents accepted; verification starts asynchronously once all required documents are available","content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"oneOf":[{"title":"Single document accepted","type":"object","required":["document_id","document_type","file_name","message"],"properties":{"document_id":{"type":"string","description":"Document identifier"},"document_type":{"type":"string"},"file_name":{"type":"string"},"message":{"type":"string","description":"Confirms that the document was accepted for processing."}}},{"title":"Batch documents accepted","type":"object","required":["uploaded","message"],"properties":{"uploaded":{"type":"array","items":{"type":"object","properties":{"document_type":{"type":"string"},"document_id":{"type":"string"},"file_name":{"type":"string"},"status":{"type":"string","enum":["uploaded"]}}}},"errors":{"type":"array","description":"Per-document failures, omitted when every document succeeds.","items":{"type":"object","properties":{"document_type":{"type":"string"},"error":{"type":"string"}}}},"message":{"type":"string"}}}]}}}}}},"400":{"description":"Bad Request - invalid file type, missing document, or user not ready for uploads"},"401":{"description":"Unauthorized"},"404":{"description":"User not found or no active KYC session"},"500":{"description":"Internal Server Error"}}}}}}
````

## Trigger KYC verification and check readiness

> Explicitly start KYC verification for a partner user on the \`direct\_data\` image flow, or check\
> what is still missing. Verification \*\*also\*\* starts automatically once an identity document +\
> selfie + KYC data are present (see the document upload endpoint) — this endpoint is an\
> explicit control and readiness check, not the only path. No request body.\
> \
> The response tells you the state:\
> \
> \- \*\*Ready, started:\*\* \`{ "ready": true, "status": "IN\_PROGRESS", "message": "Verification started." }\`.\
> &#x20; Idempotent — if a run is already in flight, the message is \`"Verification is already in progress."\`\
> &#x20; and no duplicate run is created.\
> \- \*\*Already verified:\*\* \`{ "ready": true, "status": "VERIFIED", "message": "User is already verified." }\`.\
> \- \*\*Not ready:\*\* \`{ "ready": false, "missing": \["identity\_document", "selfie"], "message": "..." }\`.\
> &#x20; The \`missing\` items are generic: \`identity\_document\` (any accepted ID type), \`selfie\`, \`kyc\_data\`.\
> \
> If the user has not submitted KYC with the \`direct\_data\` method yet, this returns \`400\` — submit\
> KYC via \`POST /api/v1/partner/users/{user\_uuid}/kyc-submissions\` first.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}}},"paths":{"/api/v1/partner/users/{user_uuid}/kyc/verify":{"post":{"tags":["User Management"],"summary":"Trigger KYC verification and check readiness","description":"Explicitly start KYC verification for a partner user on the `direct_data` image flow, or check\nwhat is still missing. Verification **also** starts automatically once an identity document +\nselfie + KYC data are present (see the document upload endpoint) — this endpoint is an\nexplicit control and readiness check, not the only path. No request body.\n\nThe response tells you the state:\n\n- **Ready, started:** `{ \"ready\": true, \"status\": \"IN_PROGRESS\", \"message\": \"Verification started.\" }`.\n  Idempotent — if a run is already in flight, the message is `\"Verification is already in progress.\"`\n  and no duplicate run is created.\n- **Already verified:** `{ \"ready\": true, \"status\": \"VERIFIED\", \"message\": \"User is already verified.\" }`.\n- **Not ready:** `{ \"ready\": false, \"missing\": [\"identity_document\", \"selfie\"], \"message\": \"...\" }`.\n  The `missing` items are generic: `identity_document` (any accepted ID type), `selfie`, `kyc_data`.\n\nIf the user has not submitted KYC with the `direct_data` method yet, this returns `400` — submit\nKYC via `POST /api/v1/partner/users/{user_uuid}/kyc-submissions` first.\n","operationId":"runPartnerKYCVerification","parameters":[{"name":"user_uuid","in":"path","required":true,"description":"Public UUID of the user","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Readiness result. Either verification was triggered / is already running / the user is\nalready verified, or the user is not ready and `missing` lists what to provide.\n","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"ready":{"type":"boolean"},"status":{"type":"string","enum":["IN_PROGRESS","VERIFIED"],"description":"Present when `ready` is true."},"missing":{"type":"array","description":"Present when `ready` is false — the required items still to provide.","items":{"type":"string","enum":["identity_document","selfie","kyc_data"]}},"message":{"type":"string"}}}}}}}},"400":{"description":"Bad Request - the user has not submitted KYC with the direct_data method yet"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"},"500":{"description":"Internal Server Error"}}}}}}
```

## Update KYC data for a partner user

> Update optional PII fields on existing KYC data. The user stays verified.\
> Only optional fields can be updated.\
> Required fields (first\_name, last\_name, country\_code) cannot be changed.\
> \
> For EUR/AUD/GBP offramp, provide address, city, and postal\_code here.\
> These will auto-populate into payment details for EUR/AUD/GBP payouts.\
> \
> \*\*Sender identity (consumer-to-consumer payout rails).\*\* Chinese mobile wallets\
> (Alipay, WeChat Pay) settle person to person: the receiving wallet must show the\
> paying customer as the remitter, not Unigox. \`POST /offramp/initiate\` returns\
> \`422 SENDER\_IDENTITY\_REQUIRED\` when the customer's record cannot name them, listing\
> the fields to send here in \`error.details.kyc\_fields\`. \`gender\`, \`nationality\`,\
> \`id\_issue\_country\` and \`source\_of\_funds\` exist for that flow, alongside \`dob\`,\
> \`phone\_number\`, \`id\_number\`, \`id\_type\` and the address fields the same rails need.\
> Send them once, then retry the order.\
> \
> The values are normalized before they are stored, and a value the rail would reject\
> is a \`400\` here rather than a failed payout later: \`gender\` accepts \`M\`/\`F\`\
> (\`MALE\`/\`FEMALE\` are folded in), \`nationality\` and \`id\_issue\_country\` take an ISO\
> 3166-1 alpha-2 code, and \`source\_of\_funds\` takes one of the listed slugs.\
> \
> The record belongs to the customer, not to a payout destination — never send another\
> person's details here to satisfy a payout.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}}},"paths":{"/api/v1/partner/users/{user_uuid}/kyc":{"patch":{"tags":["User Management"],"summary":"Update KYC data for a partner user","description":"Update optional PII fields on existing KYC data. The user stays verified.\nOnly optional fields can be updated.\nRequired fields (first_name, last_name, country_code) cannot be changed.\n\nFor EUR/AUD/GBP offramp, provide address, city, and postal_code here.\nThese will auto-populate into payment details for EUR/AUD/GBP payouts.\n\n**Sender identity (consumer-to-consumer payout rails).** Chinese mobile wallets\n(Alipay, WeChat Pay) settle person to person: the receiving wallet must show the\npaying customer as the remitter, not Unigox. `POST /offramp/initiate` returns\n`422 SENDER_IDENTITY_REQUIRED` when the customer's record cannot name them, listing\nthe fields to send here in `error.details.kyc_fields`. `gender`, `nationality`,\n`id_issue_country` and `source_of_funds` exist for that flow, alongside `dob`,\n`phone_number`, `id_number`, `id_type` and the address fields the same rails need.\nSend them once, then retry the order.\n\nThe values are normalized before they are stored, and a value the rail would reject\nis a `400` here rather than a failed payout later: `gender` accepts `M`/`F`\n(`MALE`/`FEMALE` are folded in), `nationality` and `id_issue_country` take an ISO\n3166-1 alpha-2 code, and `source_of_funds` takes one of the listed slugs.\n\nThe record belongs to the customer, not to a payout destination — never send another\nperson's details here to satisfy a payout.\n","operationId":"updateKYC","parameters":[{"name":"user_uuid","in":"path","required":true,"description":"The user_uuid returned by POST /partner/users.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City"},"postal_code":{"type":"string"},"dob":{"type":"string","format":"date","description":"Date of birth (YYYY-MM-DD). Must be in the past and indicate an age of at least 18; a future, malformed, or under-18 date is rejected with HTTP 400."},"phone_number":{"type":"string","description":"E.164 format"},"middle_name":{"type":"string"},"id_number":{"type":"string","description":"Document number, as printed on the document named by `id_type`."},"id_type":{"type":"string","enum":["NATIONAL_ID","DRIVER_LICENSE","PASSPORT","RESIDENCE_PERMIT"]},"gender":{"type":"string","enum":["M","F"],"description":"Sender identity. As it appears on the identity document — payout rails match it against the document, so this is not a self-description field. `MALE`/`FEMALE` are accepted and normalized to `M`/`F`."},"nationality":{"type":"string","description":"Sender identity. Nationality as an ISO 3166-1 alpha-2 code."},"id_issue_country":{"type":"string","description":"Sender identity. Country that issued the document, ISO 3166-1 alpha-2. Often but not always the same as `nationality`."},"source_of_funds":{"type":"string","enum":["salary","business_income","savings","investment","gift","family_support","loan","sale_of_property","inheritance","other"],"description":"Sender identity. Where the money being sent comes from. Each payout rail maps this onto its own catalogue and refuses a payout it cannot map, so send the closest listed value rather than `other` when one applies."}}}}}},"responses":{"200":{"description":"KYC data updated successfully. `updated_fields` names only the fields that actually reached the record. A recognised field sent blank is a no-op rather than an error, so a patch whose recognised fields are all blank answers `200` with `updated_fields: []` and nothing is written — this endpoint does not clear a stored value.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"updated_fields":{"type":"array","items":{"type":"string"},"description":"Fields written by this patch. Empty when every recognised field in the body was blank."},"message":{"type":"string"}}}}}}}},"400":{"description":"Bad request — no valid fields, attempting to change a required field, or a value that fails validation. Body: `{ \"success\": false, \"data\": { \"error_key\": \"...\", \"message\": \"...\" } }`. DOB-related `error_key` values: `underage_not_allowed` (age under 18) and `invalid_date_of_birth` (malformed or future date). Sender-identity values: `invalid_gender`, `invalid_country_code` (`nationality` / `id_issue_country` is not an alpha-2 code) and `invalid_source_of_funds`. `invalid_field_value` means a value was sent as an object or an array where a string or a number is expected. `no_valid_kyc_fields` means the body carried no updatable field at all. Nothing is written when one field is rejected — fix it and resend the whole patch."},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}}}}
```

## Verification status for a partner user

> Get the current verification status for a partner user. This endpoint returns the current status,\
> verification URL (if available), and other relevant information. Optionally, you can provide a\
> verification\_id to check a specific verification session.\
> \
> Status values:\
> \- \`NOT\_INITIATED\` — no verification started yet\
> \- \`PENDING\_DOCUMENTS\` — waiting for the customer to submit documents\
> \- \`IN\_PROGRESS\` — automated verification is underway (usually a couple of minutes)\
> \- \`UNDER\_REVIEW\` — under manual compliance review; the customer is being reviewed by our team\
> &#x20; and is NOT yet cleared. Unlike \`IN\_PROGRESS\` (automated, usually a couple of minutes), this can\
> &#x20; take up to 24 hours. Keep polling; treat as not cleared.\
> \- \`VERIFIED\` — verification passed; the customer is approved\
> \- \`FAILED\` — verification could not be completed due to an error\
> \- \`VERIFICATION\_REJECTED\` — verification completed with a rejection outcome\
> \
> Non-terminal states (\`NOT\_INITIATED\`, \`PENDING\_DOCUMENTS\`, \`IN\_PROGRESS\`, \`UNDER\_REVIEW\`) mean the\
> customer is not cleared yet — keep polling. A manual compliance review is now surfaced as the\
> friendly \`UNDER\_REVIEW\` (the internal review-state name is still never exposed) so you can tell a\
> long human review apart from a quick automated check and set the right expectation with your\
> customer.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"VerificationStatusResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"status":{"type":"string","enum":["NOT_INITIATED","PENDING_DOCUMENTS","IN_PROGRESS","UNDER_REVIEW","VERIFIED","FAILED","VERIFICATION_REJECTED"],"description":"Current verification status.\n\n- `NOT_INITIATED` — no verification started\n- `PENDING_DOCUMENTS` — awaiting customer document submission\n- `IN_PROGRESS` — automated verification underway (usually a couple of minutes); treat as not cleared yet\n- `UNDER_REVIEW` — under manual compliance review; the customer is being reviewed by our team and is NOT yet cleared. Unlike `IN_PROGRESS` (automated, usually a couple of minutes), this can take up to 24 hours. Keep polling; treat as not cleared.\n- `VERIFIED` — approved\n- `FAILED` — terminal error\n- `VERIFICATION_REJECTED` — completed with rejection\n"},"verification_url":{"type":"string","format":"uri","nullable":true,"description":"Verification URL (for handoff method)"},"verification_seconds_left":{"type":"integer","nullable":true,"description":"Seconds remaining for verification session (if applicable)"},"provider_messages":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Status messages or rejection reasons"}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}}}},"paths":{"/api/v1/partner/users/{user_uuid}/verification-status":{"get":{"tags":["User Management"],"summary":"Verification status for a partner user","description":"Get the current verification status for a partner user. This endpoint returns the current status,\nverification URL (if available), and other relevant information. Optionally, you can provide a\nverification_id to check a specific verification session.\n\nStatus values:\n- `NOT_INITIATED` — no verification started yet\n- `PENDING_DOCUMENTS` — waiting for the customer to submit documents\n- `IN_PROGRESS` — automated verification is underway (usually a couple of minutes)\n- `UNDER_REVIEW` — under manual compliance review; the customer is being reviewed by our team\n  and is NOT yet cleared. Unlike `IN_PROGRESS` (automated, usually a couple of minutes), this can\n  take up to 24 hours. Keep polling; treat as not cleared.\n- `VERIFIED` — verification passed; the customer is approved\n- `FAILED` — verification could not be completed due to an error\n- `VERIFICATION_REJECTED` — verification completed with a rejection outcome\n\nNon-terminal states (`NOT_INITIATED`, `PENDING_DOCUMENTS`, `IN_PROGRESS`, `UNDER_REVIEW`) mean the\ncustomer is not cleared yet — keep polling. A manual compliance review is now surfaced as the\nfriendly `UNDER_REVIEW` (the internal review-state name is still never exposed) so you can tell a\nlong human review apart from a quick automated check and set the right expectation with your\ncustomer.\n","operationId":"getVerificationStatus","parameters":[{"name":"user_uuid","in":"path","required":true,"description":"The user_uuid returned by POST /partner/users. Partners can only access their own users.","schema":{"type":"string","format":"uuid"}},{"name":"verification_id","in":"query","required":false,"description":"Optional verification session ID. If not provided, the latest verification session for the user will be checked.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successfully retrieved verification status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationStatusResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"User not found"},"500":{"description":"Internal Server Error"}}}}}}
```

## Payment details for a partner user

> Retrieve all payment details (payment profiles) for a partner user. Returns all saved beneficiary details that can be used for offramp operations. Partners can only access their own users.

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"GetPaymentDetailsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PaymentProfile"},"description":"List of all payment profiles for the user"}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PaymentProfile":{"type":"object","properties":{"payment_details_id":{"type":"string","description":"Payment details ID. Use this value in offramp/onramp quote requests."},"rail":{"type":"string","description":"Payment rail identifier"},"currency":{"type":"string","description":"Fiat currency code"},"institution_name":{"type":"string","nullable":true,"description":"Institution (bank) name if applicable"},"institution_id":{"type":"string","nullable":true,"description":"Institution identifier (our internal slug)"},"details":{"type":"object","description":"Payment details (account number, VPA, IBAN, etc.)","additionalProperties":true}}}}},"paths":{"/api/v1/partner/users/{user_uuid}/payment-details":{"get":{"tags":["User Management"],"summary":"Payment details for a partner user","description":"Retrieve all payment details (payment profiles) for a partner user. Returns all saved beneficiary details that can be used for offramp operations. Partners can only access their own users.","operationId":"getPaymentDetails","parameters":[{"name":"user_uuid","in":"path","required":true,"description":"The user_uuid returned by POST /partner/users. Partners can only access their own users.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successfully retrieved payment details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetPaymentDetailsResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"User not found"},"500":{"description":"Internal Server Error"}}}}}}
```

## Create payment details for a partner user

> Create payment details (beneficiary details) for a partner user. Used for offramp operations where crypto is converted to fiat and sent to the user's payment account. Partners can only access their own users.\
> \
> Payment details must belong to the same beneficiary represented by \`user\_uuid\`. Do not attach phone numbers, bank accounts, wallets, or other payout instruments for unrelated people to the same partner user. If you need to pay a different person, create a separate partner user for that beneficiary first.\
> \
> Institution handling:\
> \- for most rails, \`institution\_id\` must be provided from \`/api/v1/supported/institutions\`\
> \- partners may omit \`institution\_id\` only for the explicitly supported rails below; the backend will assign the generic \`other-bank\` payment method automatically\
> \
> Currently supported omission rails:\
> \- \`iban-sepa\`\
> \- \`nip-nigeria\`<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"CreatePaymentDetailsRequest":{"description":"Conditional request schema for partner payment details. `institution_id` may be omitted for `iban-sepa`, `nip-nigeria`, and `imps-neft-india`.","oneOf":[{"$ref":"#/components/schemas/CreatePaymentDetailsRequestInstitutionOptional"},{"$ref":"#/components/schemas/CreatePaymentDetailsRequestInstitutionRequired"}]},"CreatePaymentDetailsRequestInstitutionOptional":{"allOf":[{"$ref":"#/components/schemas/CreatePaymentDetailsRequestBase"},{"type":"object","properties":{"rail":{"type":"string","enum":["iban-sepa","nip-nigeria","imps-neft-india"],"description":"Payment rail identifier for rails that support omission of `institution_id`"},"institution_id":{"type":"string","description":"Optional for `iban-sepa`, `nip-nigeria`, and `imps-neft-india`.\nFor `iban-sepa`/`nip-nigeria`, if omitted the backend assigns the generic `other-bank` payment method automatically.\nFor `imps-neft-india` it is ignored entirely (resolves to the generic `imps-or-neft-transfer` method); the bank is taken from `details.bank_name` + `details.ifsc_code`.\n"}}}]},"CreatePaymentDetailsRequestBase":{"type":"object","required":["currency","rail","details"],"properties":{"currency":{"type":"string","description":"ISO 3-letter fiat currency code (e.g., INR, EUR, USD)"},"rail":{"type":"string","description":"Payment rail identifier (e.g., \"upi-india\", \"iban-sepa\", \"imps-neft-india\")"},"details":{"type":"object","description":"Payment details object with fields specific to the payment network (e.g., account_number, vpa, iban, full_name)","additionalProperties":true},"country_code":{"type":"string","description":"Optional ISO 2-letter country code"}}},"CreatePaymentDetailsRequestInstitutionRequired":{"allOf":[{"$ref":"#/components/schemas/CreatePaymentDetailsRequestBase"},{"type":"object","required":["institution_id"],"properties":{"rail":{"type":"string","description":"Payment rail identifier for rails that require institution selection","not":{"enum":["iban-sepa","nip-nigeria","imps-neft-india"]}},"institution_id":{"type":"string","description":"Institution (payment method) identifier - our internal slug (e.g., \"wise\", \"hdfc-bank\", \"revolut\", \"uala\", \"banco-santander-argentina\").\nRequired for all rails except `iban-sepa`, `nip-nigeria`, and `imps-neft-india`.\nIf omitted for any other rail, the API returns a validation error.\nFor rails with multiple formats (e.g., CVU/CBU), the format is automatically determined based on the payment method type of this institution.\n"}}}]},"CreatePaymentDetailsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PaymentProfile"}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PaymentProfile":{"type":"object","properties":{"payment_details_id":{"type":"string","description":"Payment details ID. Use this value in offramp/onramp quote requests."},"rail":{"type":"string","description":"Payment rail identifier"},"currency":{"type":"string","description":"Fiat currency code"},"institution_name":{"type":"string","nullable":true,"description":"Institution (bank) name if applicable"},"institution_id":{"type":"string","nullable":true,"description":"Institution identifier (our internal slug)"},"details":{"type":"object","description":"Payment details (account number, VPA, IBAN, etc.)","additionalProperties":true}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}},"paths":{"/api/v1/partner/users/{user_uuid}/payment-details":{"post":{"tags":["User Management"],"summary":"Create payment details for a partner user","description":"Create payment details (beneficiary details) for a partner user. Used for offramp operations where crypto is converted to fiat and sent to the user's payment account. Partners can only access their own users.\n\nPayment details must belong to the same beneficiary represented by `user_uuid`. Do not attach phone numbers, bank accounts, wallets, or other payout instruments for unrelated people to the same partner user. If you need to pay a different person, create a separate partner user for that beneficiary first.\n\nInstitution handling:\n- for most rails, `institution_id` must be provided from `/api/v1/supported/institutions`\n- partners may omit `institution_id` only for the explicitly supported rails below; the backend will assign the generic `other-bank` payment method automatically\n\nCurrently supported omission rails:\n- `iban-sepa`\n- `nip-nigeria`\n","operationId":"createPaymentDetails","parameters":[{"name":"user_uuid","in":"path","required":true,"description":"The user_uuid returned by POST /partner/users. Partners can only access their own users.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePaymentDetailsRequest"}}}},"responses":{"201":{"description":"Payment details created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePaymentDetailsResponse"}}}},"400":{"description":"Bad Request - invalid payment details or unsupported combination","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"User not found"},"422":{"description":"Recipient details are incomplete or invalid for the payout corridor. The recipient fields you supply are validated against the provider's per-corridor requirements; if a required field is missing or malformed, the recipient is not created and the offending field names are listed in the error message. Fix the details and resubmit. Address fields auto-populated from KYC are not part of this check (a missing address does not block creation — see the KYC endpoints).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"error":{"type":"string","description":"Human-readable message naming the missing or invalid recipient fields."}}}}}},"500":{"description":"Internal Server Error"}}}}}}
```

## Get a single payment detail for a partner user

> Retrieve one payment detail (payment profile) by its \`payment\_details\_id\`, returning the same shape as one element of the list endpoint (\`rail\`, \`currency\`, \`institution\_name\`, \`institution\_id\`, \`details\`).\
> \
> Use this when you already hold a \`payment\_details\_id\` instead of listing every payment detail for the user — it stays fast on accounts with many saved payment details.\
> \
> Partners can only access their own users. A payment detail that belongs to a different user (or another partner) returns \`404\` rather than revealing that it exists.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"GetPaymentDetailResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PaymentProfile"}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PaymentProfile":{"type":"object","properties":{"payment_details_id":{"type":"string","description":"Payment details ID. Use this value in offramp/onramp quote requests."},"rail":{"type":"string","description":"Payment rail identifier"},"currency":{"type":"string","description":"Fiat currency code"},"institution_name":{"type":"string","nullable":true,"description":"Institution (bank) name if applicable"},"institution_id":{"type":"string","nullable":true,"description":"Institution identifier (our internal slug)"},"details":{"type":"object","description":"Payment details (account number, VPA, IBAN, etc.)","additionalProperties":true}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}},"paths":{"/api/v1/partner/users/{user_uuid}/payment-details/{payment_details_id}":{"get":{"tags":["User Management"],"summary":"Get a single payment detail for a partner user","description":"Retrieve one payment detail (payment profile) by its `payment_details_id`, returning the same shape as one element of the list endpoint (`rail`, `currency`, `institution_name`, `institution_id`, `details`).\n\nUse this when you already hold a `payment_details_id` instead of listing every payment detail for the user — it stays fast on accounts with many saved payment details.\n\nPartners can only access their own users. A payment detail that belongs to a different user (or another partner) returns `404` rather than revealing that it exists.\n","operationId":"getPaymentDetailById","parameters":[{"name":"user_uuid","in":"path","required":true,"description":"The user_uuid returned by POST /partner/users. Partners can only access their own users.","schema":{"type":"string","format":"uuid"}},{"name":"payment_details_id","in":"path","required":true,"description":"The payment_details_id returned when creating or listing payment details.","schema":{"type":"string"}}],"responses":{"200":{"description":"Successfully retrieved the payment detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetPaymentDetailResponse"}}}},"400":{"description":"Bad Request - malformed payment_details_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized"},"404":{"description":"User not found, or payment detail not found / not owned by this user"},"500":{"description":"Internal Server Error"}}}}}}
```

## Delete payment details for a partner user

> Delete a specific payment details (payment profile) for a partner user by its ID. Partners can only access their own users.

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"User Management","description":"Endpoints to create and manage user accounts"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}}},"paths":{"/api/v1/partner/users/{user_uuid}/payment-details/{payment_details_id}":{"delete":{"tags":["User Management"],"summary":"Delete payment details for a partner user","description":"Delete a specific payment details (payment profile) for a partner user by its ID. Partners can only access their own users.","operationId":"deletePaymentDetails","parameters":[{"name":"user_uuid","in":"path","required":true,"description":"The user_uuid returned by POST /partner/users. Partners can only access their own users.","schema":{"type":"string","format":"uuid"}},{"name":"payment_details_id","in":"path","required":true,"description":"The payment_details_id returned when creating payment details.","schema":{"type":"string"}}],"responses":{"200":{"description":"Payment details deleted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"message":{"type":"string"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"User not found or payment details not found"},"409":{"description":"Conflict - payment details linked to active offers"},"500":{"description":"Internal Server Error"}}}}}}
```


# On Ramp

On-ramp operations (fiat to crypto) — partner initiates a buy order, vendor provides liquidity, end-user sends fiat

## Get on-ramp price estimate

> Get indicative on-ramp pricing (fiat to crypto) without creating a quote.\
> This endpoint is lightweight and does not reserve liquidity.\
> \
> Use this when you need quick pricing discovery before calling \`/partner/onramp/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\`).<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"On-Ramp","description":"On-ramp operations (fiat to crypto) — partner initiates a buy order, vendor provides liquidity, end-user sends fiat"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[],"paths":{"/api/v1/partner/onramp/estimate":{"post":{"tags":["On-Ramp"],"summary":"Get on-ramp price estimate","description":"Get indicative on-ramp pricing (fiat to crypto) without creating a quote.\nThis endpoint is lightweight and does not reserve liquidity.\n\nUse this when you need quick pricing discovery before calling `/partner/onramp/quote`.\nOptional filters: `payment_method_slug`, `payment_network_slug`, `provider_scope`, `country_code`.\nThis endpoint is indicative and does not apply user/payment-details ownership checks; for executable pricing use quote (which still requires an API key).\n\n**Authentication:** None. This is a public endpoint — no API key required (same as `/partner/liquidity`).\n","operationId":"getOnRampEstimate","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerPriceEstimateRequest"}}}},"responses":{"200":{"description":"Estimate retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerPriceEstimateResponse"}}}]}}}},"400":{"description":"Bad request — invalid parameters, missing required fields,\nunsupported cryptocurrency, or invalid provider_scope.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"409":{"description":"No offers available for the requested pair/amount","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"500":{"description":"Internal Server Error"}}}}},"components":{"schemas":{"PartnerPriceEstimateRequest":{"type":"object","required":["crypto_currency","fiat_currency"],"oneOf":[{"required":["crypto_amount"]},{"required":["fiat_amount"]}],"properties":{"crypto_currency":{"type":"string","description":"Cryptocurrency code (e.g., USDT, USDC)"},"fiat_currency":{"type":"string","description":"Target fiat currency code (e.g., NGN, USD, EUR)"},"crypto_amount":{"type":"string","description":"Crypto amount to estimate from. Provide exactly one of crypto_amount or fiat_amount."},"fiat_amount":{"type":"string","description":"Fiat amount to estimate from. Provide exactly one of crypto_amount or fiat_amount."},"payment_method_slug":{"type":"string","description":"Optional payment method slug filter to narrow routing"},"payment_network_slug":{"type":"string","description":"Optional payment network slug filter to narrow routing"},"provider_scope":{"type":"string","description":"Optional provider scope filter; defaults to all","enum":["all","licensed_only","p2p_only"]},"country_code":{"type":"string","description":"Optional ISO country code (alpha-2) used for fee/routing context"},"partner_fee_pct":{"type":"string","pattern":"^[0-9]+(\\.[0-9]+)?$","description":"Optional per-order markup you charge your end user, applied on the same crypto base\nas the platform fee (non-compounding). Percent units: `1` means 1%. Omitting the field\nfrom this request payload — or sending `0` — means no markup; the markup cannot be\nnegative. Fiat-anchored offramp orders have no cap — the deposit grows to cover the\nmarkup. Every other case hits a formula domain bound, not a business cap: onramp\nrejects `partner_fee_pct >= 100` (the withheld or grossed-up fee would consume the\nentire buyer amount, leaving nothing delivered); crypto-anchored offramp rejects it\nwhen the fee would consume the entire pinned deposit.\n\nNote: you send this value as a JSON **string**, but it round-trips as a JSON **number**\nin `fee_breakdown.partner_fee_pct` and in the webhook `data.partner_fee_pct` field —\ndon't parse those as strings.\n\n**Rejection reference:**\n\n| Rejection | `error.code` | HTTP status |\n|---|---|---|\n| `partner_fee_pct` is non-numeric, negative, `NaN`, or `Inf` | `INVALID_REQUEST` | 400 |\n| Onramp: fee would consume the entire buyer amount (`partner_fee_pct >= 100`) | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: fee would consume the entire pinned deposit | `INVALID_REQUEST` | 400 |\n| Fiat-anchored offramp: the computed fee is not a finite amount | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: the carved fee-exclusive gross falls outside the matched offer's min/max | `NO_OFFERS_AVAILABLE` | 409 |\n","default":"0"}}},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerPriceEstimateResponse":{"type":"object","properties":{"rate":{"type":"string","description":"Indicative crypto-to-fiat exchange rate"},"crypto_amount":{"type":"string","description":"Estimated total crypto amount — the fee-exclusive gross: the user's amount plus the platform\nfee. Partner-fee-exclusive, except for a crypto-anchored onramp estimate (request declares\n`crypto_amount`): there the amount is sized up so the delivered amount stays exact after the\npartner fee is withheld, so this field includes the markup. A crypto-anchored offramp\nestimate carves both fees out of the pinned deposit instead, so this field and\n`fee_breakdown.platform_fee` both shrink relative to a no-markup request, even though the\nmarkup itself stays out of this field. Every other case is unaffected — the markup is\nreported separately in `fee_breakdown.partner_fee`.\n\nOn-ramp: the end user would receive `crypto_amount − fee_breakdown.platform_fee −\nfee_breakdown.partner_fee` for a fiat-anchored estimate, or exactly this `crypto_amount` for a\ncrypto-anchored estimate. Off-ramp: the amount you would actually fund at initiation is\n`crypto_amount + fee_breakdown.partner_fee`.\n"},"fiat_amount":{"type":"string","description":"Estimated fiat amount"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"payment_method_slug":{"type":"string","description":"Matched payment method slug used for estimate"},"payment_network_slug":{"type":"string","description":"Matched payment network slug used for estimate"},"provider_scope":{"type":"string","description":"Applied provider scope"},"is_indicative":{"type":"boolean","description":"True when response is an estimate and not a reserving quote"}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}},"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}}}
```

## Get on-ramp quote

> Get a price quote for an on-ramp operation (fiat to crypto). 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).\
> \
> \*\*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). There's no separate transfer leg for on-ramp:\
> at release the full buyer amount lands in your partner wallet, and the markup is captured by\
> withholding it when the crypto bridges out at send-out (for a crypto-anchored order, the trade is\
> sized up beforehand so the requested amount still arrives net of the withheld fee). What the end\
> user receives: \`crypto\_amount − fee\_breakdown.platform\_fee − fee\_breakdown.partner\_fee\` for a\
> fiat-anchored order, or exactly the \`crypto\_amount\` you requested for a crypto-anchored order.\
> \
> 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). 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.\
> \
> \*\*Payment method:\*\* Provide \`payment\_method\_slug\` and/or \`payment\_network\_slug\` to narrow routing.\
> If omitted, the best available option for the pair is selected automatically.\
> \
> \*\*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. 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\`.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"On-Ramp","description":"On-ramp operations (fiat to crypto) — partner initiates a buy order, vendor provides liquidity, end-user sends fiat"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"PartnerOnRampQuoteRequest":{"type":"object","required":["user_uuid","fiat_currency","crypto_currency"],"oneOf":[{"required":["fiat_amount"]},{"required":["crypto_amount"]}],"properties":{"user_uuid":{"type":"string","format":"uuid","description":"Public UUID of the end-user (returned by `POST /partner/users` as `id`)"},"fiat_amount":{"type":"string","description":"Fiat amount the end-user will send. Provide exactly one of fiat_amount or crypto_amount."},"crypto_amount":{"type":"string","description":"Crypto amount to receive. Provide exactly one of fiat_amount or crypto_amount."},"fiat_currency":{"type":"string","description":"Source fiat currency code (ISO 4217)"},"crypto_currency":{"type":"string","description":"Target cryptocurrency code (e.g., USDT, USDC)"},"payment_method_slug":{"type":"string","description":"Optional payment method slug to narrow routing"},"payment_network_slug":{"type":"string","description":"Optional payment network slug to narrow routing"},"provider_scope":{"type":"string","description":"Optional provider scope filter; defaults to all","enum":["all","licensed_only","p2p_only"]},"country_code":{"type":"string","description":"Optional ISO 2-letter country code for fee/routing context"},"partner_fee_pct":{"type":"string","pattern":"^[0-9]+(\\.[0-9]+)?$","description":"Optional per-order markup you charge your end user, applied on the same crypto base\nas the platform fee (non-compounding). Percent units: `1` means 1%. Omitting the field\nfrom this request payload — or sending `0` — means no markup; the markup cannot be\nnegative. Fiat-anchored offramp orders have no cap — the deposit grows to cover the\nmarkup. Every other case hits a formula domain bound, not a business cap: onramp\nrejects `partner_fee_pct >= 100` (the withheld or grossed-up fee would consume the\nentire buyer amount, leaving nothing delivered); crypto-anchored offramp rejects it\nwhen the fee would consume the entire pinned deposit.\n\nNote: you send this value as a JSON **string**, but it round-trips as a JSON **number**\nin `fee_breakdown.partner_fee_pct` and in the webhook `data.partner_fee_pct` field —\ndon't parse those as strings.\n\n**Rejection reference:**\n\n| Rejection | `error.code` | HTTP status |\n|---|---|---|\n| `partner_fee_pct` is non-numeric, negative, `NaN`, or `Inf` | `INVALID_REQUEST` | 400 |\n| Onramp: fee would consume the entire buyer amount (`partner_fee_pct >= 100`) | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: fee would consume the entire pinned deposit | `INVALID_REQUEST` | 400 |\n| Fiat-anchored offramp: the computed fee is not a finite amount | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: the carved fee-exclusive gross falls outside the matched offer's min/max | `NO_OFFERS_AVAILABLE` | 409 |\n","default":"0"}}},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerOnRampQuoteResponse":{"type":"object","properties":{"quote_id":{"type":"string","format":"uuid","description":"Unique quote identifier — use this to initiate the order"},"rate":{"type":"string","description":"Fiat-to-crypto exchange rate (fiat units per 1 crypto unit)"},"crypto_amount":{"type":"string","description":"Total crypto amount for the order — the end-user's amount plus the platform fee.\nPartner-fee-exclusive on a fiat-anchored quote (request declared `fiat_amount`): the markup\nis reported separately in `fee_breakdown.partner_fee` and is not added here — the end user\nreceives `crypto_amount − fee_breakdown.platform_fee − fee_breakdown.partner_fee`, withheld\nfrom this amount at bridge send-out. On a crypto-anchored quote (request declared\n`crypto_amount`), the trade is sized up so this field includes the markup, and the end user\nreceives exactly the requested `crypto_amount`, unchanged by the markup.\n"},"fiat_amount":{"type":"string","description":"Fiat amount the end-user sends"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"payment_method_slug":{"type":"string","description":"Matched payment method slug used for this quote"},"payment_network_slug":{"type":"string","description":"Matched payment network slug used for this quote"},"expires_at":{"type":"string","format":"date-time","description":"Quote expiration timestamp (60 seconds from creation)"}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}},"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/onramp/quote":{"post":{"tags":["On-Ramp"],"summary":"Get on-ramp quote","description":"Get a price quote for an on-ramp operation (fiat to crypto). Returns the exchange rate,\namounts, and a quote ID that can be used to initiate the order. Quotes expire after 60 seconds.\n\nThe `user_uuid` is the public UUID returned by `POST /partner/users` (the `id` field).\n\n**Fee structure:** The `fee_breakdown` object shows all fee components transparently:\n`platform_fee` (our fee in crypto), `partner_fee` (your per-order markup in crypto — declare it\nby passing `partner_fee_pct` on the quote/estimate request, where `1` means 1%), `partner_fee_pct`\n(the percentage applied), and `total_fee` (sum of both). The partner fee is computed on the same\ncrypto base as the platform fee (non-compounding). There's no separate transfer leg for on-ramp:\nat release the full buyer amount lands in your partner wallet, and the markup is captured by\nwithholding it when the crypto bridges out at send-out (for a crypto-anchored order, the trade is\nsized up beforehand so the requested amount still arrives net of the withheld fee). What the end\nuser receives: `crypto_amount − fee_breakdown.platform_fee − fee_breakdown.partner_fee` for a\nfiat-anchored order, or exactly the `crypto_amount` you requested for a crypto-anchored order.\n\nApply your fee on the **same side** the platform does, or your receipts won't reconcile with our\norders. `crypto_amount` in responses and webhooks stays partner-fee-exclusive, with two\nexceptions: a crypto-anchored onramp order (request declares `crypto_amount`) is sized up so the\ndelivered amount stays exact after the markup is withheld, so `crypto_amount` there includes the\nmarkup; a crypto-anchored offramp order (request declares `crypto_amount`) carves both fees out of\nthe pinned deposit, so `crypto_amount` and `platform_fee` both shrink relative to a no-markup\nrequest even though the markup itself stays out of `crypto_amount`. Every other case is unaffected\n— the markup shows only in `fee_breakdown` (and in the funding amount returned by transfer\nauthorization for offramp). Fiat-anchored offramp orders have no cap on the markup — the deposit\ngrows to cover it. Every other case hits a formula domain bound, not a business cap: onramp\nrejects `partner_fee_pct >= 100` (the withheld or grossed-up fee would consume the entire buyer\namount, leaving nothing delivered); crypto-anchored offramp rejects it when the fee would consume\nthe entire pinned deposit.\n\n**Payment method:** Provide `payment_method_slug` and/or `payment_network_slug` to narrow routing.\nIf omitted, the best available option for the pair is selected automatically.\n\n**KYC requirement:** The customer must be KYC-cleared before you can quote or transact for them.\nIf the customer is not cleared, this endpoint returns `422 KYC_NOT_CLEARED` and\n`error.details.kyc_status` carries their current partner-facing KYC status. A customer still in\ncompliance review is reported as `IN_PROGRESS` — treat `IN_PROGRESS` as \"not cleared yet\" and\nkeep polling `GET /api/v1/partner/users/{user_uuid}/verification-status`.\n","operationId":"getOnRampQuote","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerOnRampQuoteRequest"}}}},"responses":{"200":{"description":"Quote retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerOnRampQuoteResponse"}}}]}}}},"400":{"description":"Bad request — invalid parameters or missing required fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"404":{"description":"User not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"409":{"description":"No offers available for the requested pair","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"422":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}}}
```

## Initiate on-ramp order

> Initiate an on-ramp order using a previously obtained quote. The quote must still be active\
> (not expired or already used).\
> \
> On success, returns the created order with status \`awaiting\_liquidity\_provider\`.\
> Once a vendor accepts, the order moves to \`awaiting\_vendor\_escrow\_funding\`, then\
> \`awaiting\_fiat\_transfer\` when the crypto is locked in escrow.\
> \
> At \`awaiting\_fiat\_transfer\` the partner must call \`confirm-payment-sent\` to advance the order.\
> If the order type is \`payment\_request\`, call \`submit-payer-details\` first before confirming.\
> \
> \*\*KYC requirement:\*\* The customer must be KYC-cleared, as described on \`POST /onramp/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.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"On-Ramp","description":"On-ramp operations (fiat to crypto) — partner initiates a buy order, vendor provides liquidity, end-user sends fiat"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"PartnerInitiateRequest":{"type":"object","required":["quote_id"],"properties":{"quote_id":{"type":"string","format":"uuid","description":"Quote ID from the quote endpoint. Payment details are taken from the quote."},"reference_id":{"type":"string","description":"Optional partner-supplied external reference ID (e.g. your internal order ID). Stored on the order and echoed back in responses."}}},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerInitiateResponse":{"type":"object","properties":{"order_id":{"type":"string","format":"uuid","description":"Unique order identifier for tracking"},"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"quote_id":{"type":"string","format":"uuid","description":"Quote ID that was consumed"},"reference_id":{"type":"string","nullable":true,"description":"Partner-supplied external reference ID (echoed from initiate request)"},"order_type":{"type":"string","enum":["offramp","onramp"],"description":"Direction of the order"},"amounts":{"$ref":"#/components/schemas/PartnerOrderAmounts"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/TimelineEntry"}},"created_at":{"type":"string","format":"date-time"},"recipient_context":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientContext"}],"nullable":true},"compliance":{"allOf":[{"$ref":"#/components/schemas/PartnerComplianceCase"}],"nullable":true,"description":"Durable case shown in Compliance admin for third-party payout."}}},"PartnerOrderStatus":{"type":"string","description":"Order status.\n\n**Off-ramp statuses (crypto → fiat):**\n- `created` — order created, quote locked\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_crypto_transfer_authorization` — partner must authorize crypto transfer via EIP-712 signature\n- `crypto_transfer_authorization_pending` — authorization submitted, awaiting on-chain confirmation\n- `crypto_received` — crypto locked in escrow\n- `fiat_payment_started` — buyer submitted fiat payment proof\n- `awaiting_fiat_received_confirmation` — waiting for partner to confirm fiat receipt\n- `fiat_received_confirmed` — fiat confirmed, crypto release pending\n- `crypto_release_pending` — escrow release in progress\n- `completed` — order fully completed\n- `cancelled` — order cancelled\n- `failed` — terminal error\n- `price_changed_requote_needed` — slippage exceeded at initiation (not a trade status)\n\n**On-ramp statuses (fiat → crypto):**\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_vendor_escrow_funding` — vendor accepted, funding escrow with crypto\n- `awaiting_fiat_transfer` — crypto locked in escrow, partner must confirm payment sent\n- `fiat_transfer_pending` — fiat payment confirmed, awaiting vendor confirmation of receipt\n- `completed` — vendor confirmed fiat received, crypto released to end-user wallet\n- `cancelled` — order cancelled\n- `failed` — escrow error\n\n**On-ramp send-out statuses (optional post-trade cross-chain transfer):**\n\n> After an on-ramp order reaches `completed`, the partner may optionally initiate a\n> **send-out** — a cross-chain transfer of the received crypto to a destination network\n> (e.g. XAI → Arbitrum). This is an additional flow and does not affect the core trade lifecycle.\n> The `completed` status reflects that the trade itself is done; send-out statuses track\n> the subsequent cross-chain transfer only.\n\n- `send_out_pending` — send-out initiated, cross-chain transfer in progress\n- `send_out_completed` — cross-chain transfer successfully delivered to destination\n- `send_out_failed` — cross-chain transfer failed; the on-ramp trade itself remains completed\n","enum":["created","awaiting_liquidity_provider","awaiting_crypto_transfer_authorization","crypto_transfer_authorization_pending","crypto_received","fiat_payment_started","awaiting_fiat_received_confirmation","fiat_received_confirmed","crypto_release_pending","awaiting_vendor_escrow_funding","awaiting_fiat_transfer","fiat_transfer_pending","completed","cancelled","failed","price_changed_requote_needed","send_out_pending","send_out_completed","send_out_failed"]},"PartnerOrderAmounts":{"type":"object","properties":{"crypto_amount":{"type":"string","description":"Total crypto amount (standard units) — the fee-exclusive gross: the buy/sell amount plus the\nplatform fee. Partner-fee-exclusive, except for a crypto-anchored onramp order (originally\nquoted with `crypto_amount`): there the amount was sized up so the delivered amount stays\nexact after the partner fee is withheld, so this field includes the markup. A crypto-anchored\nofframp order carves both fees out of the pinned deposit instead, so this field and\n`fee_breakdown.platform_fee` both shrink relative to a no-markup request, even though the\nmarkup itself stays out of this field. Every other case is unaffected — the markup is\nreported separately in `fee_breakdown.partner_fee`.\n\nOn-ramp — what the end user receives: `crypto_amount − fee_breakdown.platform_fee −\nfee_breakdown.partner_fee` for a fiat-anchored order, or exactly this `crypto_amount` for a\ncrypto-anchored order (both fees are withheld at bridge send-out without changing this\nfield). Off-ramp — what the partner funds at transfer authorization:\n`crypto_amount + fee_breakdown.partner_fee`.\n"},"fiat_amount":{"type":"string","description":"Fiat amount the user receives"},"crypto_currency":{"type":"string","description":"Cryptocurrency code"},"fiat_currency":{"type":"string","description":"Fiat currency code"},"rate":{"type":"string","description":"Crypto-to-fiat exchange rate"}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}},"TimelineEntry":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"timestamp":{"type":"string","format":"date-time","description":"When this status was reached"},"description":{"type":"string","description":"Human-readable description of the status change"}}},"PartnerRecipientContext":{"type":"object","required":["sender_id","recipient_id","recipient_destination_id","sender_recipient_relationship","purpose_of_payment","screening_status"],"properties":{"sender_id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"recipient_destination_id":{"type":"string","format":"uuid"},"sender_recipient_relationship":{"type":"string"},"purpose_of_payment":{"type":"string"},"purpose_details":{"type":"string"},"screening_status":{"type":"string"}}},"PartnerComplianceCase":{"type":"object","required":["case_id","status","risk_decision","requires_review"],"description":"The compliance record for a third-party payout. In v1 the controls run BEFORE the order is\ncreated, so a payout that fails one is rejected with an error rather than returned as a held\norder — see the error codes in the Third-party payouts guide.\n","properties":{"case_id":{"type":"string","format":"uuid","description":"Identifier of the compliance record — the payout's `quote_id`, so a partner and a Unigox\noperator refer to the same record.\n"},"status":{"type":"string","enum":["authorized"],"description":"v1 emits `authorized`; a rejected payout returns an error instead."},"risk_decision":{"type":"string","enum":["allow"],"description":"v1 emits `allow`; a blocking decision surfaces as an error, not a status."},"requires_review":{"type":"boolean","description":"Always false in v1. Operator review happens Unigox-side over the same record."}}},"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/onramp/initiate":{"post":{"tags":["On-Ramp"],"summary":"Initiate on-ramp order","description":"Initiate an on-ramp order using a previously obtained quote. The quote must still be active\n(not expired or already used).\n\nOn success, returns the created order with status `awaiting_liquidity_provider`.\nOnce a vendor accepts, the order moves to `awaiting_vendor_escrow_funding`, then\n`awaiting_fiat_transfer` when the crypto is locked in escrow.\n\nAt `awaiting_fiat_transfer` the partner must call `confirm-payment-sent` to advance the order.\nIf the order type is `payment_request`, call `submit-payer-details` first before confirming.\n\n**KYC requirement:** The customer must be KYC-cleared, as described on `POST /onramp/quote`.\nIf they are not, this endpoint returns `422 KYC_NOT_CLEARED` with the customer's partner-facing\nstatus in `error.details.kyc_status`. The check is re-applied as the order moves toward payout.\n","operationId":"initiateOnRamp","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerInitiateRequest"}}}},"responses":{"201":{"description":"On-ramp order created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerInitiateResponse"}}}]}}}},"400":{"description":"Bad request — missing required fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"404":{"description":"Quote not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"409":{"description":"Quote expired or already used","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"422":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}}}
```

## Submit payer details

> Submit the end-user's payment details for a \`payment\_request\` on-ramp order.\
> Only required when the order type is \`payment\_request\` — regular on-ramp orders do not need this step.\
> \
> \*\*Order types:\*\*\
> \- \*\*Regular on-ramp\*\* — the vendor sends fiat to the end-user directly. No payer details needed.\
> &#x20; The partner receives vendor payment details in the \`vendor\_payment\_details\` field of \`GET /orders/:id\`\
> &#x20; and forwards them to the end-user to initiate the transfer.\
> \- \*\*\`payment\_request\` on-ramp\*\* — the vendor sends a payment request (e.g. M-Pesa push) to the end-user.\
> &#x20; The end-user's account details must be submitted here first so the vendor knows where to send the request.\
> &#x20; Check \`payment\_request: true\` in the order response to identify this type.\
> \
> Call this endpoint after initiating the order and before calling \`confirm-payment-sent\`.\
> The order must be in \`awaiting\_fiat\_transfer\` status.\
> \
> Provide either a \`payment\_details\_id\` (referencing a previously saved payment profile) or\
> an inline \`payment\_details\` object with the payer's account information.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"On-Ramp","description":"On-ramp operations (fiat to crypto) — partner initiates a buy order, vendor provides liquidity, end-user sends fiat"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"SubmitPayerDetailsRequest":{"type":"object","description":"Submit the payer's payment details for a `payment_request` on-ramp order.\nProvide exactly one of `payment_details_id` (saved profile) or `payment_details` (inline object).\n","oneOf":[{"required":["payment_details_id"],"properties":{"payment_details_id":{"type":"integer","format":"int64","description":"ID of a previously saved payment profile for the end-user"}}},{"required":["payment_details"],"properties":{"payment_details":{"type":"object","required":["payment_method_id","payment_network_id","details"],"properties":{"payment_method_id":{"type":"integer","format":"int64","description":"Payment method ID"},"payment_network_id":{"type":"integer","format":"int64","description":"Payment network ID"},"details":{"type":"object","description":"Payer's account details (fields depend on payment method)","additionalProperties":true},"country_code":{"type":"string","description":"Optional ISO 2-letter country code"}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/orders/{order_id}/submit-payer-details":{"post":{"tags":["On-Ramp"],"summary":"Submit payer details","description":"Submit the end-user's payment details for a `payment_request` on-ramp order.\nOnly required when the order type is `payment_request` — regular on-ramp orders do not need this step.\n\n**Order types:**\n- **Regular on-ramp** — the vendor sends fiat to the end-user directly. No payer details needed.\n  The partner receives vendor payment details in the `vendor_payment_details` field of `GET /orders/:id`\n  and forwards them to the end-user to initiate the transfer.\n- **`payment_request` on-ramp** — the vendor sends a payment request (e.g. M-Pesa push) to the end-user.\n  The end-user's account details must be submitted here first so the vendor knows where to send the request.\n  Check `payment_request: true` in the order response to identify this type.\n\nCall this endpoint after initiating the order and before calling `confirm-payment-sent`.\nThe order must be in `awaiting_fiat_transfer` status.\n\nProvide either a `payment_details_id` (referencing a previously saved payment profile) or\nan inline `payment_details` object with the payer's account information.\n","operationId":"submitPayerDetails","parameters":[{"name":"order_id","in":"path","required":true,"description":"Order UUID returned by the initiate endpoint","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitPayerDetailsRequest"}}}},"responses":{"200":{"description":"Payer details submitted successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string"}}}}}]}}}},"400":{"description":"Bad request — invalid or missing payer details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"409":{"description":"Order is not in the correct status or not a payment_request order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}}}
```

## Confirm fiat payment sent

> Confirm that the end-user has initiated the fiat payment to the vendor.\
> This advances the order from \`awaiting\_fiat\_transfer\` to \`fiat\_transfer\_pending\`,\
> signalling to the vendor that the fiat is on its way.\
> \
> For \`payment\_request\` orders, call \`submit-payer-details\` first.\
> No request body required.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"On-Ramp","description":"On-ramp operations (fiat to crypto) — partner initiates a buy order, vendor provides liquidity, end-user sends fiat"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/orders/{order_id}/confirm-payment-sent":{"post":{"tags":["On-Ramp"],"summary":"Confirm fiat payment sent","description":"Confirm that the end-user has initiated the fiat payment to the vendor.\nThis advances the order from `awaiting_fiat_transfer` to `fiat_transfer_pending`,\nsignalling to the vendor that the fiat is on its way.\n\nFor `payment_request` orders, call `submit-payer-details` first.\nNo request body required.\n","operationId":"confirmPaymentSent","parameters":[{"name":"order_id","in":"path","required":true,"description":"Order UUID","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Payment confirmed successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"order_id":{"type":"string","format":"uuid"},"status":{"type":"string"}}}}}]}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"409":{"description":"Order is not in awaiting_fiat_transfer status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}}}
```

## Get send-out authorization parameters

> After an on-ramp order reaches \`completed\` status (crypto on master wallet), the partner\
> can optionally initiate a cross-chain send-out to transfer the USDT to an external network\
> (e.g. Arbitrum).\
> \
> This endpoint returns the EIP-712 \`ForwardRequest\` parameters required to authorize the\
> transfer. The partner must sign the request locally and submit the signature to\
> \`POST /api/v1/partner/orders/{order\_id}/authorize-bridge\`.\
> \
> \*\*Prerequisites:\*\*\
> \- Order status must be \`completed\` (internal: \`escrow\_released\_to\_buyer\`)\
> \- \`destination\_chain\` must be a supported chain slug (e.g. \`arbitrum\`, \`ethereum\`)\
> \- \`destination\_address\` must be a valid EVM address\
> \
> \*\*Response includes:\*\*\
> \- \`forward\_request\` — pre-filled EIP-712 ForwardRequest object to sign\
> \- \`eip712\_domain\` — domain parameters for EIP-712 signing\
> \- \`quote\_id\` — Relay quote ID embedded in the transfer calldata\
> \- \`to\_amount\` — estimated USDT amount to be received on the destination chain (atomic units)\
> \- \`forwarder\_nonce\` — current on-chain nonce for the master wallet<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"On-Ramp","description":"On-ramp operations (fiat to crypto) — partner initiates a buy order, vendor provides liquidity, end-user sends fiat"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/orders/{order_id}/bridge-authorization-parameters":{"get":{"tags":["On-Ramp"],"summary":"Get send-out authorization parameters","description":"After an on-ramp order reaches `completed` status (crypto on master wallet), the partner\ncan optionally initiate a cross-chain send-out to transfer the USDT to an external network\n(e.g. Arbitrum).\n\nThis endpoint returns the EIP-712 `ForwardRequest` parameters required to authorize the\ntransfer. The partner must sign the request locally and submit the signature to\n`POST /api/v1/partner/orders/{order_id}/authorize-bridge`.\n\n**Prerequisites:**\n- Order status must be `completed` (internal: `escrow_released_to_buyer`)\n- `destination_chain` must be a supported chain slug (e.g. `arbitrum`, `ethereum`)\n- `destination_address` must be a valid EVM address\n\n**Response includes:**\n- `forward_request` — pre-filled EIP-712 ForwardRequest object to sign\n- `eip712_domain` — domain parameters for EIP-712 signing\n- `quote_id` — Relay quote ID embedded in the transfer calldata\n- `to_amount` — estimated USDT amount to be received on the destination chain (atomic units)\n- `forwarder_nonce` — current on-chain nonce for the master wallet\n","operationId":"getBridgeAuthorizationParameters","parameters":[{"name":"order_id","in":"path","required":true,"description":"Order UUID","schema":{"type":"string","format":"uuid"}},{"name":"destination_chain","in":"query","required":true,"description":"Destination chain slug (e.g. `arbitrum`, `ethereum`)","schema":{"type":"string"}},{"name":"destination_address","in":"query","required":true,"description":"Recipient EVM address on the destination chain","schema":{"type":"string"}}],"responses":{"200":{"description":"Authorization parameters returned successfully","content":{"application/json":{}}},"400":{"description":"Invalid destination_chain or destination_address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"409":{"description":"Order is not in `completed` status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}}}
```

## Authorize send-out (cross-chain transfer)

> Submit the signed EIP-712 ForwardRequest to initiate a cross-chain send-out.\
> The signature is verified on-chain via the XAI forwarder contract.\
> \
> Call \`GET bridge-authorization-parameters\` first to get the parameters to sign.\
> \
> After submission the order transitions to \`send\_out\_pending\`. A background worker\
> monitors the Relay bridge and updates the status to \`send\_out\_completed\` or\
> \`send\_out\_failed\` when the transfer settles. The partner receives a webhook for\
> each transition.\
> \
> When the send-out completes, the \`send\_out\_tx\_hash\` field in \`GET /orders/:id\`\
> contains the destination-chain transaction hash.\
> \
> \*\*This endpoint is idempotent at the on-chain level\*\* — the EIP-712 nonce prevents\
> duplicate submissions. A second call with the same nonce will be rejected.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"On-Ramp","description":"On-ramp operations (fiat to crypto) — partner initiates a buy order, vendor provides liquidity, end-user sends fiat"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/orders/{order_id}/authorize-bridge":{"post":{"tags":["On-Ramp"],"summary":"Authorize send-out (cross-chain transfer)","description":"Submit the signed EIP-712 ForwardRequest to initiate a cross-chain send-out.\nThe signature is verified on-chain via the XAI forwarder contract.\n\nCall `GET bridge-authorization-parameters` first to get the parameters to sign.\n\nAfter submission the order transitions to `send_out_pending`. A background worker\nmonitors the Relay bridge and updates the status to `send_out_completed` or\n`send_out_failed` when the transfer settles. The partner receives a webhook for\neach transition.\n\nWhen the send-out completes, the `send_out_tx_hash` field in `GET /orders/:id`\ncontains the destination-chain transaction hash.\n\n**This endpoint is idempotent at the on-chain level** — the EIP-712 nonce prevents\nduplicate submissions. A second call with the same nonce will be rejected.\n","operationId":"authorizeBridge","parameters":[{"name":"order_id","in":"path","required":true,"description":"Order UUID","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["destination_chain","destination_address","forward_request","signature"],"properties":{"destination_chain":{"type":"string","description":"Destination chain slug (must match the value used in bridge-authorization-parameters)"},"destination_address":{"type":"string","description":"Recipient EVM address on the destination chain"},"forward_request":{"type":"object","description":"EIP-712 ForwardRequest object (values from bridge-authorization-parameters)","required":["from","to","value","gas","nonce","deadline","data"],"properties":{"from":{"type":"string"},"to":{"type":"string"},"value":{"type":"string"},"gas":{"type":"string"},"nonce":{"type":"string"},"deadline":{"type":"string"},"data":{"type":"string"}}},"signature":{"type":"string","description":"EIP-712 signature of the ForwardRequest (hex, with 0x prefix)"}}}}}},"responses":{"200":{"description":"Send-out submitted successfully","content":{"application/json":{}}},"400":{"description":"Invalid request — bad forward_request, nonce mismatch, or invalid destination","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"409":{"description":"Order is not in `completed` status or send-out already in progress","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"502":{"description":"Transactor service unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}}}
```


# Liquidity

Real-time aggregated liquidity data — available trading pairs, amount ranges, and payment methods

## Get aggregated liquidity by trading pairs

> Returns real-time aggregated liquidity data across all active off-ramp offers.\
> \
> Use this endpoint for \*\*liquidity discovery\*\* — to determine which crypto/fiat pairs\
> are currently available, what fiat amount ranges are supported, and which payment\
> methods are accepted. No pricing or vendor details are returned.\
> \
> \*\*Trade type semantics:\*\* \`trade\_type\` is expressed in partner terms.\
> \`SELL\` means the partner sells crypto and receives fiat (the most common off-ramp direction).\
> \`BUY\` means the partner buys crypto by sending fiat.\
> \
> \*\*Eligibility:\*\* Only offers that pass the same gates as the vendor-matching engine\
> are included — the offer must be authorized, the vendor must be active and online,\
> and at least one payment method option must be active.\
> \
> \*\*Aggregation:\*\* Results are grouped by \`(crypto\_currency\_code, fiat\_currency\_code, trade\_type)\`.\
> Amount ranges span the minimum and maximum across all eligible offers in the group.\
> Payment methods are deduplicated and sorted alphabetically.\
> \
> \*\*Authentication:\*\* None. This is a public endpoint — no API key required.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Liquidity","description":"Real-time aggregated liquidity data — available trading pairs, amount ranges, and payment methods"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[],"paths":{"/api/v1/partner/liquidity":{"get":{"tags":["Liquidity"],"summary":"Get aggregated liquidity by trading pairs","description":"Returns real-time aggregated liquidity data across all active off-ramp offers.\n\nUse this endpoint for **liquidity discovery** — to determine which crypto/fiat pairs\nare currently available, what fiat amount ranges are supported, and which payment\nmethods are accepted. No pricing or vendor details are returned.\n\n**Trade type semantics:** `trade_type` is expressed in partner terms.\n`SELL` means the partner sells crypto and receives fiat (the most common off-ramp direction).\n`BUY` means the partner buys crypto by sending fiat.\n\n**Eligibility:** Only offers that pass the same gates as the vendor-matching engine\nare included — the offer must be authorized, the vendor must be active and online,\nand at least one payment method option must be active.\n\n**Aggregation:** Results are grouped by `(crypto_currency_code, fiat_currency_code, trade_type)`.\nAmount ranges span the minimum and maximum across all eligible offers in the group.\nPayment methods are deduplicated and sorted alphabetically.\n\n**Authentication:** None. This is a public endpoint — no API key required.\n","responses":{"200":{"description":"Liquidity pairs retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"pairs":{"type":"array","items":{"$ref":"#/components/schemas/LiquidityPair"}}}}}}}}},"500":{"description":"Internal server error"}}}}},"components":{"schemas":{"LiquidityPair":{"type":"object","required":["crypto_currency_code","fiat_currency_code","trade_type","min_amount","max_amount","min_amount_usd","max_amount_usd","payment_methods"],"properties":{"crypto_currency_code":{"type":"string","description":"Cryptocurrency code (e.g. USDC, BTC)"},"fiat_currency_code":{"type":"string","description":"Fiat currency code (ISO 4217)"},"trade_type":{"type":"string","enum":["BUY","SELL"],"description":"Partner-facing trade direction.\n`SELL` — partner sells crypto and receives fiat (off-ramp).\n`BUY` — partner buys crypto by sending fiat (on-ramp).\n"},"min_amount":{"type":"number","format":"double","description":"Minimum fiat amount accepted across all eligible offers, in the pair's native fiat currency"},"max_amount":{"type":"number","format":"double","description":"Maximum fiat amount accepted across all eligible offers, in the pair's native fiat currency"},"min_amount_usd":{"type":"number","format":"double","description":"Minimum fiat amount converted to USD"},"max_amount_usd":{"type":"number","format":"double","description":"Maximum fiat amount converted to USD"},"payment_methods":{"type":"array","items":{"type":"string"},"description":"Deduplicated, alphabetically sorted list of payment method slugs available for this pair"}}}}}}
```


# Off Ramp

Off-ramp operations (crypto to fiat) — sell crypto and receive fiat

## Get off-ramp price estimate

> 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.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Off-Ramp","description":"Off-ramp operations (crypto to fiat) — sell crypto and receive fiat"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[],"paths":{"/api/v1/partner/offramp/estimate":{"post":{"tags":["Off-Ramp"],"summary":"Get off-ramp price estimate","description":"Get indicative off-ramp pricing (crypto to fiat) without creating a quote.\nPricing 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).\nThis endpoint is lightweight and does not reserve liquidity.\n\nUse this when you need quick pricing discovery before calling `/partner/offramp/quote`.\nOptional filters: `payment_method_slug`, `payment_network_slug`, `provider_scope`, `country_code`.\nThis endpoint is indicative and does not apply user/payment-details ownership checks; for executable pricing use quote (which still requires an API key).\n\n**Authentication:** None. This is a public endpoint — no API key required (same as `/partner/liquidity`).\n\n**Note:** EUR, AUD, and GBP off-ramp payouts require the user's physical\naddress. Ensure the user's KYC includes `address`, `city`, and `postal_code` before initiating.\nSee `PATCH /partner/users/{user_uuid}/kyc` to update.\n","operationId":"getOffRampEstimate","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerPriceEstimateRequest"}}}},"responses":{"200":{"description":"Estimate retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerPriceEstimateResponse"}}}]}}}},"400":{"description":"Bad request — invalid parameters, missing required fields, \nunsupported cryptocurrency, or invalid provider_scope.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"409":{"description":"No offers available for the requested pair/amount","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"500":{"description":"Internal Server Error"}}}}},"components":{"schemas":{"PartnerPriceEstimateRequest":{"type":"object","required":["crypto_currency","fiat_currency"],"oneOf":[{"required":["crypto_amount"]},{"required":["fiat_amount"]}],"properties":{"crypto_currency":{"type":"string","description":"Cryptocurrency code (e.g., USDT, USDC)"},"fiat_currency":{"type":"string","description":"Target fiat currency code (e.g., NGN, USD, EUR)"},"crypto_amount":{"type":"string","description":"Crypto amount to estimate from. Provide exactly one of crypto_amount or fiat_amount."},"fiat_amount":{"type":"string","description":"Fiat amount to estimate from. Provide exactly one of crypto_amount or fiat_amount."},"payment_method_slug":{"type":"string","description":"Optional payment method slug filter to narrow routing"},"payment_network_slug":{"type":"string","description":"Optional payment network slug filter to narrow routing"},"provider_scope":{"type":"string","description":"Optional provider scope filter; defaults to all","enum":["all","licensed_only","p2p_only"]},"country_code":{"type":"string","description":"Optional ISO country code (alpha-2) used for fee/routing context"},"partner_fee_pct":{"type":"string","pattern":"^[0-9]+(\\.[0-9]+)?$","description":"Optional per-order markup you charge your end user, applied on the same crypto base\nas the platform fee (non-compounding). Percent units: `1` means 1%. Omitting the field\nfrom this request payload — or sending `0` — means no markup; the markup cannot be\nnegative. Fiat-anchored offramp orders have no cap — the deposit grows to cover the\nmarkup. Every other case hits a formula domain bound, not a business cap: onramp\nrejects `partner_fee_pct >= 100` (the withheld or grossed-up fee would consume the\nentire buyer amount, leaving nothing delivered); crypto-anchored offramp rejects it\nwhen the fee would consume the entire pinned deposit.\n\nNote: you send this value as a JSON **string**, but it round-trips as a JSON **number**\nin `fee_breakdown.partner_fee_pct` and in the webhook `data.partner_fee_pct` field —\ndon't parse those as strings.\n\n**Rejection reference:**\n\n| Rejection | `error.code` | HTTP status |\n|---|---|---|\n| `partner_fee_pct` is non-numeric, negative, `NaN`, or `Inf` | `INVALID_REQUEST` | 400 |\n| Onramp: fee would consume the entire buyer amount (`partner_fee_pct >= 100`) | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: fee would consume the entire pinned deposit | `INVALID_REQUEST` | 400 |\n| Fiat-anchored offramp: the computed fee is not a finite amount | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: the carved fee-exclusive gross falls outside the matched offer's min/max | `NO_OFFERS_AVAILABLE` | 409 |\n","default":"0"}}},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerPriceEstimateResponse":{"type":"object","properties":{"rate":{"type":"string","description":"Indicative crypto-to-fiat exchange rate"},"crypto_amount":{"type":"string","description":"Estimated total crypto amount — the fee-exclusive gross: the user's amount plus the platform\nfee. Partner-fee-exclusive, except for a crypto-anchored onramp estimate (request declares\n`crypto_amount`): there the amount is sized up so the delivered amount stays exact after the\npartner fee is withheld, so this field includes the markup. A crypto-anchored offramp\nestimate carves both fees out of the pinned deposit instead, so this field and\n`fee_breakdown.platform_fee` both shrink relative to a no-markup request, even though the\nmarkup itself stays out of this field. Every other case is unaffected — the markup is\nreported separately in `fee_breakdown.partner_fee`.\n\nOn-ramp: the end user would receive `crypto_amount − fee_breakdown.platform_fee −\nfee_breakdown.partner_fee` for a fiat-anchored estimate, or exactly this `crypto_amount` for a\ncrypto-anchored estimate. Off-ramp: the amount you would actually fund at initiation is\n`crypto_amount + fee_breakdown.partner_fee`.\n"},"fiat_amount":{"type":"string","description":"Estimated fiat amount"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"payment_method_slug":{"type":"string","description":"Matched payment method slug used for estimate"},"payment_network_slug":{"type":"string","description":"Matched payment network slug used for estimate"},"provider_scope":{"type":"string","description":"Applied provider scope"},"is_indicative":{"type":"boolean","description":"True when response is an estimate and not a reserving quote"}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}},"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}}}
```

## Get off-ramp quote

> 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\_id\` for an account owned by \`user\_uuid\`.\
> \- Third-party payout: omit \`payment\_details\_id\` and provide \`sender\_id\`, \`recipient\_id\`,\
> &#x20; \`recipient\_destination\_id\`, \`sender\_recipient\_relationship\`, and \`purpose\_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\`:\*\*\
> \
> \| Field | Anchor | Guarantee |\
> \|---|---|---|\
> \| \`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 \`failed\` status\
> &#x20; and the deposit is automatically refunded from escrow back to the partner wallet. Create a new quote\
> &#x20; to retry.\
> \- \`slippage\_tolerance.type=amount\` in fiat-anchored mode is evaluated as crypto-side delta, not fiat-side\
> &#x20; (fiat is always exact by construction).\
> \- Integer-minor-unit currencies (NGN, UGX, TZS, KES, GHS): \`fiat\_amount\` must 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; cancelled or failed orders refund the full amount, markup included.\
> \
> 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\`.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Off-Ramp","description":"Off-ramp operations (crypto to fiat) — sell crypto and receive fiat"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"PartnerQuoteRequest":{"type":"object","required":["user_uuid","crypto_currency","fiat_currency"],"allOf":[{"oneOf":[{"required":["crypto_amount"]},{"required":["fiat_amount"]}]},{"oneOf":[{"required":["payment_details_id"]},{"required":["sender_id","recipient_id","recipient_destination_id","sender_recipient_relationship","purpose_of_payment"]}]}],"properties":{"user_uuid":{"type":"string","format":"uuid","description":"Public UUID of the end-user (returned by `POST /partner/users` as `id`)"},"payment_details_id":{"type":"integer","format":"int64","description":"Required for a self-payout. MUST be omitted for a third-party payout, which is routed by `recipient_destination_id`. Sending both is rejected with `400 INVALID_REQUEST` — the two modes are mutually exclusive and neither silently wins."},"sender_id":{"type":"string","format":"uuid","description":"Canonical KYC subject who is funding the third-party payout. Required for third-party\npayout and must equal the real sender represented by `user_uuid`.\n"},"recipient_id":{"type":"string","format":"uuid","description":"Partner-owned recipient returned by `POST /partner/recipients`."},"recipient_destination_id":{"type":"string","format":"uuid","description":"Active destination owned by `recipient_id`; replaces `payment_details_id` for third-party payout."},"sender_recipient_relationship":{"type":"string","minLength":1,"description":"Payment-level relationship between the real sender and recipient, for example `supplier` or `family`."},"purpose_of_payment":{"type":"string","minLength":1,"description":"Canonical payment purpose used for compliance and translated only at the vendor boundary."},"purpose_details":{"type":"string","description":"Optional invoice or free-text clarification of the payment purpose."},"crypto_currency":{"type":"string","description":"Cryptocurrency code (e.g., USDT, USDC)"},"crypto_amount":{"type":"string","description":"Amount of crypto to sell (standard units, e.g., \"100.00\" for 100 USDT).\nProvide exactly one of `crypto_amount` or `fiat_amount`.\nWhen provided, the order is **crypto-anchored** — exact crypto cost is guaranteed, fiat payout varies by rate at settlement.\n"},"fiat_amount":{"type":"string","description":"Exact fiat amount the recipient will receive (e.g., \"500000\" for 500,000 NGN).\nProvide exactly one of `crypto_amount` or `fiat_amount`.\nWhen provided, the order is **fiat-anchored** — exact fiat delivery is guaranteed, crypto cost may vary within a 1.20× safety bound.\nFor integer-minor-unit currencies (NGN, UGX, TZS, KES, GHS), must be a whole number.\n"},"fiat_currency":{"type":"string","description":"Target fiat currency code (e.g., NGN, USD, EUR)"},"rail":{"type":"string","description":"Optional rail hint; must be compatible with payment details if provided"},"route":{"type":"string","description":"Optional route hint; must be compatible with payment details if provided"},"provider_scope":{"type":"string","description":"Optional provider scope filter; defaults to all","enum":["all","licensed_only","p2p_only"]},"slippage_tolerance":{"allOf":[{"$ref":"#/components/schemas/SlippageTolerance"}],"description":"Optional. Maximum tolerated price movement between `/quote` and `/initiate`,\nand between `/initiate` and vendor acceptance. When omitted, the default is\n`percent` / `0` — any rate movement returns `PRICE_CHANGED_REQUOTE` and the\npartner must request a fresh quote. Set this to opt in to silent execution\nfor small market movements.\n"},"has_fiat_settlement_notification":{"type":"boolean","description":"When `true`, only match liquidity providers that send fiat settlement\nnotifications. Orders from these providers complete automatically when\nfiat is delivered. When omitted or `false`, all providers are eligible.\n"},"partner_fee_pct":{"type":"string","pattern":"^[0-9]+(\\.[0-9]+)?$","description":"Optional per-order markup you charge your end user, applied on the same crypto base\nas the platform fee (non-compounding). Percent units: `1` means 1%. Omitting the field\nfrom this request payload — or sending `0` — means no markup; the markup cannot be\nnegative. Fiat-anchored offramp orders have no cap — the deposit grows to cover the\nmarkup. Every other case hits a formula domain bound, not a business cap: onramp\nrejects `partner_fee_pct >= 100` (the withheld or grossed-up fee would consume the\nentire buyer amount, leaving nothing delivered); crypto-anchored offramp rejects it\nwhen the fee would consume the entire pinned deposit.\n\nNote: you send this value as a JSON **string**, but it round-trips as a JSON **number**\nin `fee_breakdown.partner_fee_pct` and in the webhook `data.partner_fee_pct` field —\ndon't parse those as strings.\n\n**Rejection reference:**\n\n| Rejection | `error.code` | HTTP status |\n|---|---|---|\n| `partner_fee_pct` is non-numeric, negative, `NaN`, or `Inf` | `INVALID_REQUEST` | 400 |\n| Onramp: fee would consume the entire buyer amount (`partner_fee_pct >= 100`) | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: fee would consume the entire pinned deposit | `INVALID_REQUEST` | 400 |\n| Fiat-anchored offramp: the computed fee is not a finite amount | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: the carved fee-exclusive gross falls outside the matched offer's min/max | `NO_OFFERS_AVAILABLE` | 409 |\n","default":"0"}}},"SlippageTolerance":{"type":"object","properties":{"type":{"type":"string","enum":["percent","amount","fiat"],"description":"Tolerance type:\n- `percent` — percentage of the quoted rate (e.g., 1.5 = 1.5%)\n- `amount` — absolute fiat amount (e.g., 50.00 = 50 NGN)\n- `fiat` — alias of `amount`\n"},"value":{"type":"number","format":"double","description":"Tolerance value (percentage or fiat amount depending on type)"}}},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerQuoteResponse":{"type":"object","properties":{"quote_id":{"type":"string","format":"uuid","description":"Unique quote identifier — use this to initiate the order"},"anchor_type":{"type":"string","enum":["crypto","fiat"],"description":"Indicates which amount is guaranteed at settlement.\n- `crypto` — `crypto_amount` is exact; `fiat_amount` may vary by the rate at settlement.\n- `fiat` — `fiat_amount` is exact; `crypto_amount` may vary within a 1.20× safety bound.\n"},"rate":{"type":"string","description":"Crypto-to-fiat exchange rate at quote time"},"crypto_amount":{"type":"string","description":"Total crypto amount — the fee-exclusive gross: the sell amount plus the platform fee.\nPartner-fee-exclusive: your markup is reported separately in `fee_breakdown.partner_fee` and\nis not added here. This is not the total you fund: the amount you actually fund at initiation\nis `crypto_amount + fee_breakdown.partner_fee` (see the transfer authorization endpoint).\n"},"fiat_amount":{"type":"string","description":"Fiat amount the recipient receives"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"payment_details_id":{"type":"integer","format":"int64","description":"Payment details ID used for this quote"},"payment_method_id":{"type":"integer","format":"int64","description":"Matched payment method ID"},"payment_network_id":{"type":"integer","format":"int64","description":"Matched payment network ID"},"expires_at":{"type":"string","format":"date-time","description":"Quote expiration timestamp (30 seconds from creation)"},"has_fiat_settlement_notification":{"type":"boolean","description":"Whether the matched liquidity provider sends fiat settlement notifications.\nWhen `true`, the order will complete automatically when fiat is delivered\nto the recipient. When `false`, you must call `confirm-fiat-received`.\nYou can call `confirm-fiat-received` at any time regardless of this value.\n"},"recipient_context":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientContext"}],"nullable":true,"description":"Immutable canonical recipient snapshot for a third-party quote; absent for self-payout."}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}},"PartnerRecipientContext":{"type":"object","required":["sender_id","recipient_id","recipient_destination_id","sender_recipient_relationship","purpose_of_payment","screening_status"],"properties":{"sender_id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"recipient_destination_id":{"type":"string","format":"uuid"},"sender_recipient_relationship":{"type":"string"},"purpose_of_payment":{"type":"string"},"purpose_details":{"type":"string"},"screening_status":{"type":"string"}}},"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/offramp/quote":{"post":{"tags":["Off-Ramp"],"summary":"Get off-ramp quote","description":"Get a price quote for an off-ramp operation (crypto to fiat). Returns the exchange rate,\namounts, and a quote ID that can be used to initiate the order. Quotes expire after 60 seconds.\n\nThe `user_uuid` is the public UUID returned by `POST /partner/users` (the `id` field).\n\n**Choose exactly one payout ownership mode:**\n- Self-payout: provide `payment_details_id` for an account owned by `user_uuid`.\n- Third-party payout: omit `payment_details_id` and provide `sender_id`, `recipient_id`,\n  `recipient_destination_id`, `sender_recipient_relationship`, and `purpose_of_payment`.\n\nFor a third-party payout, `sender_id` must identify the same real KYC subject as `user_uuid`.\nAt quote time Unigox validates tenant ownership, sender eligibility, destination currency/rail,\nrecipient screening, relationship, and purpose. It persists an immutable canonical recipient\nsnapshot on the quote. Updating or archiving the recipient afterwards does not mutate the quote;\nrequest a new quote to use changed details.\n\nOn `/initiate`, Unigox revalidates the quote snapshot and creates a durable compliance case.\nIn v1 there is no partner-visible review state machine: every control is enforced BEFORE the\norder exists, so a payout that fails one is rejected with an error code rather than returned\nas a held order. An order that was created was authorized (`compliance.status: authorized`,\n`risk_decision: allow`, `requires_review: false`). Operator review happens on the Unigox side\nover the same records; API-created third-party payouts use the same Compliance queue as\nportal-created payouts.\n\n**Anchor mode — provide exactly one of `crypto_amount` or `fiat_amount`:**\n\n| Field | Anchor | Guarantee |\n|---|---|---|\n| `crypto_amount` | Crypto-anchored | Exact crypto cost; fiat payout varies by the rate at settlement |\n| `fiat_amount` | Fiat-anchored | Exact fiat delivery to recipient; crypto cost may vary within a 1.20× safety bound |\n\nThe response `anchor_type` field tells you which side is guaranteed: `\"crypto\"` or `\"fiat\"`.\n\n**Fiat-anchored notes:**\n- Useful for exact-payout use cases (e.g. deliver exactly 500,000 NGN).\n- If the crypto cost we quote exceeds 1.20× the expected amount, the order moves to `failed` status\n  and the deposit is automatically refunded from escrow back to the partner wallet. Create a new quote\n  to retry.\n- `slippage_tolerance.type=amount` in fiat-anchored mode is evaluated as crypto-side delta, not fiat-side\n  (fiat is always exact by construction).\n- Integer-minor-unit currencies (NGN, UGX, TZS, KES, GHS): `fiat_amount` must be a whole number.\n\n**Fee structure:** The `fee_breakdown` object shows all fee components transparently:\n`platform_fee` (our fee in crypto), `partner_fee` (your per-order markup in crypto — declare it\nby passing `partner_fee_pct` on the quote/estimate request, where `1` means 1%), `partner_fee_pct`\n(the percentage applied), and `total_fee` (sum of both). The partner fee is computed on the same\ncrypto base as the platform fee (non-compounding) and is settled on-chain to your partner wallet\non successful release; cancelled or failed orders refund the full amount, markup included.\n\nApply your fee on the **same side** the platform does, or your receipts won't reconcile with our\norders. `crypto_amount` in responses and webhooks stays partner-fee-exclusive, with two\nexceptions: a crypto-anchored onramp order (request declares `crypto_amount`) is sized up so the\ndelivered amount stays exact after the markup is withheld, so `crypto_amount` there includes the\nmarkup; a crypto-anchored offramp order (request declares `crypto_amount`) carves both fees out of\nthe pinned deposit, so `crypto_amount` and `platform_fee` both shrink relative to a no-markup\nrequest even though the markup itself stays out of `crypto_amount`. Every other case is unaffected\n— the markup shows only in `fee_breakdown` (and in the funding amount returned by transfer\nauthorization for offramp — `crypto_amount + fee_breakdown.partner_fee` is what you actually fund).\nFiat-anchored offramp orders have no cap on the markup — the deposit grows to cover it. Every\nother case hits a formula domain bound, not a business cap: onramp rejects `partner_fee_pct >= 100`\n(the withheld or grossed-up fee would consume the entire buyer amount, leaving nothing delivered);\ncrypto-anchored offramp rejects it when the fee would consume the entire pinned deposit.\n\n**Address requirement for EUR/AUD/GBP:** EUR, AUD, and GBP payouts require the\nuser's physical address (street, city, postal code). If the user's\nKYC does not include address data, the quote response will include a `beneficiary_requirements`\nfield indicating which fields are needed. You can update the user's KYC with\n`PATCH /partner/users/{user_uuid}/kyc` to provide the address before or after initiating the order.\nIf you initiate without address, the order will be created with status `pending_address` until\nthe address is provided.\n\n**KYC requirement:** The customer must be KYC-cleared before you can quote or transact for them.\nIf the customer is not cleared, this endpoint returns `422 KYC_NOT_CLEARED` and\n`error.details.kyc_status` carries their current partner-facing KYC status. The same check is\nre-applied as the order moves toward payout. A customer still in compliance review is reported as\n`IN_PROGRESS` — treat `IN_PROGRESS` as \"not cleared yet\" and keep polling\n`GET /api/v1/partner/users/{user_uuid}/verification-status`.\n","operationId":"getOffRampQuote","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerQuoteRequest"}}}},"responses":{"200":{"description":"Quote retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerQuoteResponse"}}}]}}}},"400":{"description":"Bad request — invalid parameters or missing required fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"404":{"description":"User or payment details not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"409":{"description":"No offers available for the requested pair","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"422":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}}}
```

## Initiate off-ramp order

> 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:\*\*\
> 1\. Submit KYC with address/city/postal\_code (\`POST /kyc-submissions\`)\
> 2\. Create payment details (\`POST /payment-details\`) — address auto-populated from KYC\
> 3\. Get quote (\`POST /offramp/quote\`)\
> 4\. 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.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Off-Ramp","description":"Off-ramp operations (crypto to fiat) — sell crypto and receive fiat"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"PartnerInitiateRequest":{"type":"object","required":["quote_id"],"properties":{"quote_id":{"type":"string","format":"uuid","description":"Quote ID from the quote endpoint. Payment details are taken from the quote."},"reference_id":{"type":"string","description":"Optional partner-supplied external reference ID (e.g. your internal order ID). Stored on the order and echoed back in responses."}}},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerInitiateResponse":{"type":"object","properties":{"order_id":{"type":"string","format":"uuid","description":"Unique order identifier for tracking"},"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"quote_id":{"type":"string","format":"uuid","description":"Quote ID that was consumed"},"reference_id":{"type":"string","nullable":true,"description":"Partner-supplied external reference ID (echoed from initiate request)"},"order_type":{"type":"string","enum":["offramp","onramp"],"description":"Direction of the order"},"amounts":{"$ref":"#/components/schemas/PartnerOrderAmounts"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/TimelineEntry"}},"created_at":{"type":"string","format":"date-time"},"recipient_context":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientContext"}],"nullable":true},"compliance":{"allOf":[{"$ref":"#/components/schemas/PartnerComplianceCase"}],"nullable":true,"description":"Durable case shown in Compliance admin for third-party payout."}}},"PartnerOrderStatus":{"type":"string","description":"Order status.\n\n**Off-ramp statuses (crypto → fiat):**\n- `created` — order created, quote locked\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_crypto_transfer_authorization` — partner must authorize crypto transfer via EIP-712 signature\n- `crypto_transfer_authorization_pending` — authorization submitted, awaiting on-chain confirmation\n- `crypto_received` — crypto locked in escrow\n- `fiat_payment_started` — buyer submitted fiat payment proof\n- `awaiting_fiat_received_confirmation` — waiting for partner to confirm fiat receipt\n- `fiat_received_confirmed` — fiat confirmed, crypto release pending\n- `crypto_release_pending` — escrow release in progress\n- `completed` — order fully completed\n- `cancelled` — order cancelled\n- `failed` — terminal error\n- `price_changed_requote_needed` — slippage exceeded at initiation (not a trade status)\n\n**On-ramp statuses (fiat → crypto):**\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_vendor_escrow_funding` — vendor accepted, funding escrow with crypto\n- `awaiting_fiat_transfer` — crypto locked in escrow, partner must confirm payment sent\n- `fiat_transfer_pending` — fiat payment confirmed, awaiting vendor confirmation of receipt\n- `completed` — vendor confirmed fiat received, crypto released to end-user wallet\n- `cancelled` — order cancelled\n- `failed` — escrow error\n\n**On-ramp send-out statuses (optional post-trade cross-chain transfer):**\n\n> After an on-ramp order reaches `completed`, the partner may optionally initiate a\n> **send-out** — a cross-chain transfer of the received crypto to a destination network\n> (e.g. XAI → Arbitrum). This is an additional flow and does not affect the core trade lifecycle.\n> The `completed` status reflects that the trade itself is done; send-out statuses track\n> the subsequent cross-chain transfer only.\n\n- `send_out_pending` — send-out initiated, cross-chain transfer in progress\n- `send_out_completed` — cross-chain transfer successfully delivered to destination\n- `send_out_failed` — cross-chain transfer failed; the on-ramp trade itself remains completed\n","enum":["created","awaiting_liquidity_provider","awaiting_crypto_transfer_authorization","crypto_transfer_authorization_pending","crypto_received","fiat_payment_started","awaiting_fiat_received_confirmation","fiat_received_confirmed","crypto_release_pending","awaiting_vendor_escrow_funding","awaiting_fiat_transfer","fiat_transfer_pending","completed","cancelled","failed","price_changed_requote_needed","send_out_pending","send_out_completed","send_out_failed"]},"PartnerOrderAmounts":{"type":"object","properties":{"crypto_amount":{"type":"string","description":"Total crypto amount (standard units) — the fee-exclusive gross: the buy/sell amount plus the\nplatform fee. Partner-fee-exclusive, except for a crypto-anchored onramp order (originally\nquoted with `crypto_amount`): there the amount was sized up so the delivered amount stays\nexact after the partner fee is withheld, so this field includes the markup. A crypto-anchored\nofframp order carves both fees out of the pinned deposit instead, so this field and\n`fee_breakdown.platform_fee` both shrink relative to a no-markup request, even though the\nmarkup itself stays out of this field. Every other case is unaffected — the markup is\nreported separately in `fee_breakdown.partner_fee`.\n\nOn-ramp — what the end user receives: `crypto_amount − fee_breakdown.platform_fee −\nfee_breakdown.partner_fee` for a fiat-anchored order, or exactly this `crypto_amount` for a\ncrypto-anchored order (both fees are withheld at bridge send-out without changing this\nfield). Off-ramp — what the partner funds at transfer authorization:\n`crypto_amount + fee_breakdown.partner_fee`.\n"},"fiat_amount":{"type":"string","description":"Fiat amount the user receives"},"crypto_currency":{"type":"string","description":"Cryptocurrency code"},"fiat_currency":{"type":"string","description":"Fiat currency code"},"rate":{"type":"string","description":"Crypto-to-fiat exchange rate"}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}},"TimelineEntry":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"timestamp":{"type":"string","format":"date-time","description":"When this status was reached"},"description":{"type":"string","description":"Human-readable description of the status change"}}},"PartnerRecipientContext":{"type":"object","required":["sender_id","recipient_id","recipient_destination_id","sender_recipient_relationship","purpose_of_payment","screening_status"],"properties":{"sender_id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"recipient_destination_id":{"type":"string","format":"uuid"},"sender_recipient_relationship":{"type":"string"},"purpose_of_payment":{"type":"string"},"purpose_details":{"type":"string"},"screening_status":{"type":"string"}}},"PartnerComplianceCase":{"type":"object","required":["case_id","status","risk_decision","requires_review"],"description":"The compliance record for a third-party payout. In v1 the controls run BEFORE the order is\ncreated, so a payout that fails one is rejected with an error rather than returned as a held\norder — see the error codes in the Third-party payouts guide.\n","properties":{"case_id":{"type":"string","format":"uuid","description":"Identifier of the compliance record — the payout's `quote_id`, so a partner and a Unigox\noperator refer to the same record.\n"},"status":{"type":"string","enum":["authorized"],"description":"v1 emits `authorized`; a rejected payout returns an error instead."},"risk_decision":{"type":"string","enum":["allow"],"description":"v1 emits `allow`; a blocking decision surfaces as an error, not a status."},"requires_review":{"type":"boolean","description":"Always false in v1. Operator review happens Unigox-side over the same record."}}},"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/offramp/initiate":{"post":{"tags":["Off-Ramp"],"summary":"Initiate off-ramp order","description":"Initiate an off-ramp order using a previously obtained quote. The quote must still be active\n(not expired or already used). The system refreshes the price and checks slippage tolerance\nbefore creating the order.\n\nIf the price has moved beyond the slippage tolerance, a `PRICE_CHANGED_REQUOTE` error is returned\nwith details about the old and new rates, so the partner can request a new quote.\n\nOn success, returns the created order with status `awaiting_liquidity_provider`.\n\n**Address requirement for EUR/AUD/GBP:** For EUR/AUD/GBP orders, if the\nuser's KYC does not include a physical address, the order will be created with status\n`pending_address`. The response will include `required_fields: [\"address\", \"city\", \"postal_code\"]`\nand `next_action: \"provide_address\"`. Call `PATCH /partner/users/{user_uuid}/kyc` to add the\naddress, and the order will automatically advance.\n\n**Recommended flow for EUR/AUD/GBP:**\n1. Submit KYC with address/city/postal_code (`POST /kyc-submissions`)\n2. Create payment details (`POST /payment-details`) — address auto-populated from KYC\n3. Get quote (`POST /offramp/quote`)\n4. Initiate (`POST /offramp/initiate`) — proceeds without delays\n\n**KYC requirement:** The customer must be KYC-cleared, exactly as described on\n`POST /offramp/quote`. If they are not, this endpoint returns `422 KYC_NOT_CLEARED` with the\ncustomer's partner-facing status in `error.details.kyc_status`. The check is re-applied as the\norder moves toward payout.\n","operationId":"initiateOffRamp","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerInitiateRequest"}}}},"responses":{"201":{"description":"Off-ramp order created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerInitiateResponse"}}}]}}}},"400":{"description":"Bad request — missing required fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"404":{"description":"Quote not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"409":{"description":"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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"422":{"description":"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.\n`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.\n`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).\n`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.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}}}
```


# Recipients

Register and manage third parties paid by KYC-verified senders

## List recipients

> Lists third parties registered by the authenticated partner. A recipient is the person or\
> business a KYC-verified sender pays; it is not the sender's own payment profile.\
> Results are tenant-scoped and never expose an unmasked account number.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Recipients","description":"Register and manage third parties paid by KYC-verified senders"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerRecipientListResponse":{"type":"object","required":["recipients","total","limit","offset"],"properties":{"recipients":{"type":"array","items":{"$ref":"#/components/schemas/PartnerRecipient"}},"total":{"type":"integer","minimum":0},"limit":{"type":"integer","minimum":1},"offset":{"type":"integer","minimum":0}}},"PartnerRecipient":{"type":"object","required":["id","kind","recipient_name","recipient_country","lifecycle","version","screening_status","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["individual","business"]},"recipient_name":{"type":"string"},"recipient_native_name":{"type":"string","nullable":true},"recipient_country":{"type":"string"},"recipient_id_type":{"type":"string","nullable":true},"recipient_id_number":{"type":"string","nullable":true,"description":"Masked partner-visible identity value; full value is never returned."},"recipient_business_registration_number":{"type":"string","nullable":true,"description":"Masked when returned."},"lifecycle":{"type":"string","enum":["draft","active","archived"]},"version":{"type":"integer","minimum":1},"screening_status":{"type":"string","enum":["pending","cleared","hit","manual_review"]},"screened_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}},"paths":{"/api/v1/partner/recipients":{"get":{"tags":["Recipients"],"summary":"List recipients","description":"Lists third parties registered by the authenticated partner. A recipient is the person or\nbusiness a KYC-verified sender pays; it is not the sender's own payment profile.\nResults are tenant-scoped and never expose an unmasked account number.\n","operationId":"listPartnerRecipients","parameters":[{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by recipient name or partner-visible identifier."},{"in":"query","name":"screening_status","schema":{"type":"string"}},{"in":"query","name":"recipient_country","schema":{"type":"string","minLength":2,"maxLength":2}},{"in":"query","name":"currency","schema":{"type":"string","minLength":3,"maxLength":3}},{"in":"query","name":"rail","schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"in":"query","name":"offset","schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Recipients returned","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerRecipientListResponse"}}}]}}}},"401":{"description":"Partner not authenticated"},"500":{"description":"Internal Server Error"}}}}}}
```

## Register a recipient

> Registers a partner-scoped third-party recipient identity. Add payout destinations\
> separately with \`POST /recipients/{recipient\_id}/destinations\`.\
> Vendor-specific terms such as beneficiary or receiver are intentionally absent.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Recipients","description":"Register and manage third parties paid by KYC-verified senders"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"PartnerCreateRecipientRequest":{"type":"object","required":["recipient_name","recipient_country"],"properties":{"recipient_kind":{"type":"string","enum":["individual","business"],"default":"individual"},"recipient_name":{"type":"string"},"recipient_native_name":{"type":"string"},"recipient_country":{"type":"string","minLength":2,"maxLength":2},"recipient_id_type":{"type":"string"},"recipient_id_number":{"type":"string","writeOnly":true},"recipient_business_registration_number":{"type":"string","writeOnly":true}}},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerRecipientResponse":{"type":"object","required":["recipient"],"properties":{"recipient":{"$ref":"#/components/schemas/PartnerRecipient"},"recipient_destination":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientDestination"}],"nullable":true}}},"PartnerRecipient":{"type":"object","required":["id","kind","recipient_name","recipient_country","lifecycle","version","screening_status","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["individual","business"]},"recipient_name":{"type":"string"},"recipient_native_name":{"type":"string","nullable":true},"recipient_country":{"type":"string"},"recipient_id_type":{"type":"string","nullable":true},"recipient_id_number":{"type":"string","nullable":true,"description":"Masked partner-visible identity value; full value is never returned."},"recipient_business_registration_number":{"type":"string","nullable":true,"description":"Masked when returned."},"lifecycle":{"type":"string","enum":["draft","active","archived"]},"version":{"type":"integer","minimum":1},"screening_status":{"type":"string","enum":["pending","cleared","hit","manual_review"]},"screened_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"PartnerRecipientDestination":{"type":"object","required":["id","recipient_id","payment_method_id","payment_network_id","country_code","currency","version","lifecycle","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"payment_method_id":{"type":"integer","format":"int64"},"payment_network_id":{"type":"integer","format":"int64"},"country_code":{"type":"string","minLength":2,"maxLength":2},"currency":{"type":"string"},"details":{"type":"object","additionalProperties":true,"description":"The rail's own fields, as submitted. On reads, values that identify an account or a person are masked to last-4 (`account_number`, `bank_account_number`, `iban`, `card_number`, `id_number`, `national_id`, `tax_id`, `mobile_number`, `phone_number`, `msisdn`); everything else — names, `bank_name`, `province`, `branch`, routing codes, `beneficiary_type` — is returned in full so destinations stay distinguishable."},"version":{"type":"integer","minimum":1},"lifecycle":{"type":"string","enum":["active","archived"]},"created_at":{"type":"string","format":"date-time"}}}}},"paths":{"/api/v1/partner/recipients":{"post":{"tags":["Recipients"],"summary":"Register a recipient","description":"Registers a partner-scoped third-party recipient identity. Add payout destinations\nseparately with `POST /recipients/{recipient_id}/destinations`.\nVendor-specific terms such as beneficiary or receiver are intentionally absent.\n","operationId":"createPartnerRecipient","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerCreateRecipientRequest"}}}},"responses":{"200":{"description":"Recipient registered","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerRecipientResponse"}}}]}}}},"400":{"description":"Invalid recipient. `error.message` names what to fix; `error.details.reason` carries a\nstable machine slug. Screening runs asynchronously AFTER creation, so it never rejects\nhere — a recipient is created with `screening_status: pending` and becomes usable for a\nquote only once it reaches `cleared`.\n"},"401":{"description":"Partner not authenticated"},"500":{"description":"Internal Server Error"},"503":{"description":"Recipient directory unreachable. The request was not wrong; retry with backoff."}}}}}}
```

## GET /api/v1/partner/recipients/{recipient\_id}

> Get recipient

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Recipients","description":"Register and manage third parties paid by KYC-verified senders"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerRecipientResponse":{"type":"object","required":["recipient"],"properties":{"recipient":{"$ref":"#/components/schemas/PartnerRecipient"},"recipient_destination":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientDestination"}],"nullable":true}}},"PartnerRecipient":{"type":"object","required":["id","kind","recipient_name","recipient_country","lifecycle","version","screening_status","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["individual","business"]},"recipient_name":{"type":"string"},"recipient_native_name":{"type":"string","nullable":true},"recipient_country":{"type":"string"},"recipient_id_type":{"type":"string","nullable":true},"recipient_id_number":{"type":"string","nullable":true,"description":"Masked partner-visible identity value; full value is never returned."},"recipient_business_registration_number":{"type":"string","nullable":true,"description":"Masked when returned."},"lifecycle":{"type":"string","enum":["draft","active","archived"]},"version":{"type":"integer","minimum":1},"screening_status":{"type":"string","enum":["pending","cleared","hit","manual_review"]},"screened_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"PartnerRecipientDestination":{"type":"object","required":["id","recipient_id","payment_method_id","payment_network_id","country_code","currency","version","lifecycle","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"payment_method_id":{"type":"integer","format":"int64"},"payment_network_id":{"type":"integer","format":"int64"},"country_code":{"type":"string","minLength":2,"maxLength":2},"currency":{"type":"string"},"details":{"type":"object","additionalProperties":true,"description":"The rail's own fields, as submitted. On reads, values that identify an account or a person are masked to last-4 (`account_number`, `bank_account_number`, `iban`, `card_number`, `id_number`, `national_id`, `tax_id`, `mobile_number`, `phone_number`, `msisdn`); everything else — names, `bank_name`, `province`, `branch`, routing codes, `beneficiary_type` — is returned in full so destinations stay distinguishable."},"version":{"type":"integer","minimum":1},"lifecycle":{"type":"string","enum":["active","archived"]},"created_at":{"type":"string","format":"date-time"}}}}},"paths":{"/api/v1/partner/recipients/{recipient_id}":{"get":{"tags":["Recipients"],"summary":"Get recipient","operationId":"getPartnerRecipient","responses":{"200":{"description":"Recipient returned","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerRecipientResponse"}}}]}}}},"404":{"description":"Recipient not found for this partner"}}}}}}
```

## Archive recipient

> Archives the recipient for future use. Existing immutable order snapshots remain auditable.

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Recipients","description":"Register and manage third parties paid by KYC-verified senders"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}}},"paths":{"/api/v1/partner/recipients/{recipient_id}":{"delete":{"tags":["Recipients"],"summary":"Archive recipient","description":"Archives the recipient for future use. Existing immutable order snapshots remain auditable.","operationId":"archivePartnerRecipient","responses":{"200":{"description":"Recipient archived"},"404":{"description":"Recipient not found for this partner"}}}}}}
```

## Update recipient identity

> Updates recipient identity by creating a new immutable version. Existing quotes and orders\
> keep their original snapshot; request a new quote to use the updated data.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Recipients","description":"Register and manage third parties paid by KYC-verified senders"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"PartnerUpdateRecipientRequest":{"type":"object","minProperties":1,"properties":{"recipient_name":{"type":"string"},"recipient_native_name":{"type":"string"},"recipient_country":{"type":"string","minLength":2,"maxLength":2},"recipient_id_type":{"type":"string"},"recipient_id_number":{"type":"string","writeOnly":true},"recipient_business_registration_number":{"type":"string","writeOnly":true}}},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerRecipientResponse":{"type":"object","required":["recipient"],"properties":{"recipient":{"$ref":"#/components/schemas/PartnerRecipient"},"recipient_destination":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientDestination"}],"nullable":true}}},"PartnerRecipient":{"type":"object","required":["id","kind","recipient_name","recipient_country","lifecycle","version","screening_status","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["individual","business"]},"recipient_name":{"type":"string"},"recipient_native_name":{"type":"string","nullable":true},"recipient_country":{"type":"string"},"recipient_id_type":{"type":"string","nullable":true},"recipient_id_number":{"type":"string","nullable":true,"description":"Masked partner-visible identity value; full value is never returned."},"recipient_business_registration_number":{"type":"string","nullable":true,"description":"Masked when returned."},"lifecycle":{"type":"string","enum":["draft","active","archived"]},"version":{"type":"integer","minimum":1},"screening_status":{"type":"string","enum":["pending","cleared","hit","manual_review"]},"screened_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"PartnerRecipientDestination":{"type":"object","required":["id","recipient_id","payment_method_id","payment_network_id","country_code","currency","version","lifecycle","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"payment_method_id":{"type":"integer","format":"int64"},"payment_network_id":{"type":"integer","format":"int64"},"country_code":{"type":"string","minLength":2,"maxLength":2},"currency":{"type":"string"},"details":{"type":"object","additionalProperties":true,"description":"The rail's own fields, as submitted. On reads, values that identify an account or a person are masked to last-4 (`account_number`, `bank_account_number`, `iban`, `card_number`, `id_number`, `national_id`, `tax_id`, `mobile_number`, `phone_number`, `msisdn`); everything else — names, `bank_name`, `province`, `branch`, routing codes, `beneficiary_type` — is returned in full so destinations stay distinguishable."},"version":{"type":"integer","minimum":1},"lifecycle":{"type":"string","enum":["active","archived"]},"created_at":{"type":"string","format":"date-time"}}}}},"paths":{"/api/v1/partner/recipients/{recipient_id}":{"patch":{"tags":["Recipients"],"summary":"Update recipient identity","description":"Updates recipient identity by creating a new immutable version. Existing quotes and orders\nkeep their original snapshot; request a new quote to use the updated data.\n","operationId":"updatePartnerRecipient","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerUpdateRecipientRequest"}}}},"responses":{"200":{"description":"Updated recipient returned","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerRecipientResponse"}}}]}}}},"400":{"description":"Invalid update"},"404":{"description":"Recipient not found for this partner"}}}}}}
```

## List recipient destinations

> Returns masked payout destinations belonging to this recipient and partner.

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Recipients","description":"Register and manage third parties paid by KYC-verified senders"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerRecipientDestinationsResponse":{"type":"object","required":["recipient_destinations"],"properties":{"recipient_destinations":{"type":"array","items":{"$ref":"#/components/schemas/PartnerRecipientDestination"}}}},"PartnerRecipientDestination":{"type":"object","required":["id","recipient_id","payment_method_id","payment_network_id","country_code","currency","version","lifecycle","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"payment_method_id":{"type":"integer","format":"int64"},"payment_network_id":{"type":"integer","format":"int64"},"country_code":{"type":"string","minLength":2,"maxLength":2},"currency":{"type":"string"},"details":{"type":"object","additionalProperties":true,"description":"The rail's own fields, as submitted. On reads, values that identify an account or a person are masked to last-4 (`account_number`, `bank_account_number`, `iban`, `card_number`, `id_number`, `national_id`, `tax_id`, `mobile_number`, `phone_number`, `msisdn`); everything else — names, `bank_name`, `province`, `branch`, routing codes, `beneficiary_type` — is returned in full so destinations stay distinguishable."},"version":{"type":"integer","minimum":1},"lifecycle":{"type":"string","enum":["active","archived"]},"created_at":{"type":"string","format":"date-time"}}}}},"paths":{"/api/v1/partner/recipients/{recipient_id}/destinations":{"get":{"tags":["Recipients"],"summary":"List recipient destinations","description":"Returns masked payout destinations belonging to this recipient and partner.","operationId":"listPartnerRecipientDestinations","parameters":[{"in":"path","name":"recipient_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Masked destinations returned","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerRecipientDestinationsResponse"}}}]}}}},"404":{"description":"Recipient not found for this partner"}}}}}}
```

## Add a recipient destination

> Adds a payout route for this recipient. \`rail\` and \`institution\_id\` are resolved to\
> the platform payment-network and payment-method IDs. \`details\` is validated against\
> the selected rail configuration. Relationship and purpose are supplied later on the quote.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Recipients","description":"Register and manage third parties paid by KYC-verified senders"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"PartnerRecipientDestinationInput":{"type":"object","required":["country_code","currency","rail","institution_id","details"],"properties":{"country_code":{"type":"string","minLength":2,"maxLength":2},"currency":{"type":"string","minLength":3,"maxLength":3},"rail":{"type":"string"},"institution_id":{"type":"string","description":"Institution identifier returned by the supported-institutions endpoint."},"details":{"type":"object","additionalProperties":true,"description":"Rail-specific fields validated using the selected payment network configuration."}}},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerRecipientDestination":{"type":"object","required":["id","recipient_id","payment_method_id","payment_network_id","country_code","currency","version","lifecycle","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"payment_method_id":{"type":"integer","format":"int64"},"payment_network_id":{"type":"integer","format":"int64"},"country_code":{"type":"string","minLength":2,"maxLength":2},"currency":{"type":"string"},"details":{"type":"object","additionalProperties":true,"description":"The rail's own fields, as submitted. On reads, values that identify an account or a person are masked to last-4 (`account_number`, `bank_account_number`, `iban`, `card_number`, `id_number`, `national_id`, `tax_id`, `mobile_number`, `phone_number`, `msisdn`); everything else — names, `bank_name`, `province`, `branch`, routing codes, `beneficiary_type` — is returned in full so destinations stay distinguishable."},"version":{"type":"integer","minimum":1},"lifecycle":{"type":"string","enum":["active","archived"]},"created_at":{"type":"string","format":"date-time"}}}}},"paths":{"/api/v1/partner/recipients/{recipient_id}/destinations":{"post":{"tags":["Recipients"],"summary":"Add a recipient destination","description":"Adds a payout route for this recipient. `rail` and `institution_id` are resolved to\nthe platform payment-network and payment-method IDs. `details` is validated against\nthe selected rail configuration. Relationship and purpose are supplied later on the quote.\n","operationId":"createPartnerRecipientDestination","parameters":[{"in":"path","name":"recipient_id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerRecipientDestinationInput"}}}},"responses":{"200":{"description":"Destination created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerRecipientDestination"}}}]}}}},"400":{"description":"The destination was rejected. `error.message` is the validator's own diagnostic and\nnames the offending field — e.g. `unknown field 'company_name' is not allowed for this\npayment network` when `details.beneficiary_type` was omitted for a company, or\n`field 'mobile_number' does not match required pattern`.\n"},"404":{"description":"Recipient not found for this partner"},"503":{"description":"Recipient directory unreachable. The request was not wrong; retry with backoff."}}}}}}
```


# Orders

Order management, status tracking, and lifecycle actions

## Order details

> Get the current status, amounts, and full timeline for a specific order.\
> The \`next\_action\` field indicates what the partner \*\*must\*\* do to advance the order.\
> \`null\` means no action is required from the partner at this stage — the order is waiting\
> for the counterparty (e.g. the buyer to submit fiat payment proof).\
> \
> The \`allowed\_actions\` array lists all API actions the partner \*\*can\*\* perform at the current\
> status. An action can be in \`allowed\_actions\` even when \`next\_action\` is \`null\` — this means\
> the action is optional. For example, \`confirm-fiat-received\` is available at \`crypto\_received\`\
> as a fast-track: if you already know the fiat has arrived (via your own systems), you can\
> confirm immediately without waiting for the buyer's payment proof flow to complete.\
> \
> \*\*Order flow summary:\*\*\
> \
> \| Status | next\_action | Who acts |\
> \|---|---|---|\
> \| \`created\` | — | Partner (or wait for vendor match) |\
> \| \`awaiting\_crypto\_transfer\_authorization\` | \`authorize\_crypto\_transfer\` | \*\*Partner\*\* |\
> \| \`crypto\_transfer\_authorization\_pending\` | — | System (on-chain confirmation) |\
> \| \`crypto\_received\` | — | \*\*Buyer\*\* submits fiat payment proof |\
> \| \`fiat\_payment\_started\` | — | System reviews proof |\
> \| \`fiat\_payment\_review\_started\` | — | System accepts/rejects proof |\
> \| \`awaiting\_fiat\_received\_confirmation\` | \`confirm\_fiat\_received\` | \*\*Partner\*\* |\
> \| \`completed\` | — | — |\
> \
> \*\*Fiat settlement and confirmation:\*\*\
> \
> You can call \`confirm-fiat-received\` at any time after crypto is in escrow.\
> You do not need to wait for a system notification. If you know through your\
> own systems that the recipient has received the fiat payment, confirm immediately.\
> \
> Some liquidity providers also send automatic settlement confirmations when the\
> recipient receives the fiat. When the order's \`has\_fiat\_settlement\_notification\`\
> is \`true\` (see the quote/order response), the order moves to \`completed\` on its own\
> and you receive the \`status=completed\` webhook — no manual confirm needed. If you\
> already confirmed manually, the auto-confirmation is a no-op; if the auto-confirmation\
> arrives first, your manual confirm call is also a no-op. Both paths are safe. See the\
> Webhooks page → "Settlement notifications" for details.\
> \
> \*\*How to implement:\*\*\
> \- Always implement a \`confirm-fiat-received\` call in your flow.\
> \- Listen for webhooks. If you receive \`status=completed\`, no further action needed.\
> \- If \`next\_action=confirm\_fiat\_received\`, call \`confirm-fiat-received\` when you\
> &#x20; know fiat has been received.\
> \- Do not wait for \`next\_action\` if you already have confirmation from your\
> &#x20; own banking or payment systems.\
> \
> \*\*Possible statuses:\*\* \`created\`, \`awaiting\_liquidity\_provider\`, \`awaiting\_crypto\_transfer\_authorization\`, \`crypto\_transfer\_authorization\_pending\`, \`crypto\_received\`, \`fiat\_payment\_started\`, \`awaiting\_fiat\_received\_confirmation\`, \`fiat\_received\_confirmed\`, \`crypto\_release\_pending\`, \`completed\`, \`cancelled\`, \`failed\`, \`price\_changed\_requote\_needed\`.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Orders","description":"Order management, status tracking, and lifecycle actions"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerOrderResponse":{"type":"object","properties":{"order_id":{"type":"string","format":"uuid","description":"Unique order identifier"},"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"next_action":{"type":"string","nullable":true,"description":"The action the partner **must** perform to advance the order. `null` means\nno action is required from the partner at this stage — the order is waiting\nfor the counterparty or for an automated system step.\n\nPossible values:\n- `authorize_crypto_transfer` — partner must authorize escrow funding\n- `confirm_fiat_received` — partner must confirm they received the fiat payment\n- `request_new_quote` — price changed, partner must request a new quote\n- `update_kyc_address` — address fields are required for EUR/AUD/GBP payouts; see `action_required` for the endpoint and fields to submit\n- `null` — no required action (waiting for buyer or system)\n","enum":["authorize_crypto_transfer","confirm_fiat_received","request_new_quote","update_kyc_address",null]},"action_required":{"type":"object","nullable":true,"description":"Present when the order needs partner intervention before it can proceed.\nContains machine-readable type, human-readable message, the API endpoint to call,\nand the fields to provide. Null/omitted when no action is needed.\n","required":["type","message"],"properties":{"type":{"type":"string","description":"Machine-readable action identifier"},"message":{"type":"string","description":"Human-readable explanation of what is needed and why"},"endpoint":{"type":"string","description":"API endpoint to call to resolve this action"},"fields":{"type":"array","description":"Fields the partner needs to provide","items":{"type":"object","required":["field","label"],"properties":{"field":{"type":"string","description":"API field name"},"label":{"type":"string","description":"Human-readable description of the field"}}}}}},"allowed_actions":{"type":"array","items":{"type":"string"},"description":"List of API actions the partner **can** perform at the current status. An action\nmay be present here even when `next_action` is `null` — meaning it is optional.\n\nNotable case: `confirm-fiat-received` appears at both `crypto_received` and\n`awaiting_fiat_received_confirmation`. At `crypto_received` it is a **fast-track**:\nif you already know the fiat has arrived via your own systems, you can confirm\nimmediately. Otherwise, wait for the buyer's proof flow to complete — the order\nwill reach `awaiting_fiat_received_confirmation` automatically.\n\nStatus-to-actions mapping:\n- `created` → `[\"cancel\"]`\n- `awaiting_liquidity_provider` → `[\"cancel\"]`\n- `awaiting_crypto_transfer_authorization` → `[\"authorize-crypto-transfer\", \"cancel\"]`\n- `crypto_transfer_authorization_pending` → `[\"cancel\"]`\n- `crypto_received` → `[\"confirm-fiat-received\"]` (optional fast-track)\n- `fiat_payment_started` → `[\"confirm-fiat-received\"]` (optional fast-track after buyer proof submission)\n- `awaiting_fiat_received_confirmation` → `[\"confirm-fiat-received\"]` (required)\n- `fiat_received_confirmed`, `crypto_release_pending`, `completed`, `cancelled`, `failed` → `[]`\n"},"crypto_transfer_authorization_seconds_left":{"type":"integer","format":"int64","nullable":true,"description":"Remaining seconds to authorize crypto transfer while order is in\n`awaiting_crypto_transfer_authorization` / `crypto_transfer_authorization_pending`.\n`null` for all other statuses.\n"},"amounts":{"$ref":"#/components/schemas/PartnerOrderAmounts"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/TimelineEntry"},"description":"Chronological list of status changes"},"has_fiat_settlement_notification":{"type":"boolean","description":"Whether the matched liquidity provider sends fiat settlement notifications.\nWhen `true`, the order will complete automatically when fiat is delivered.\nWhen `false`, you must call `confirm-fiat-received`. You can call\n`confirm-fiat-received` at any time regardless of this value.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"order_type":{"type":"string","nullable":true,"description":"Order type. Present for on-ramp orders. Absent for off-ramp orders.\n- `onramp` — buy crypto with fiat\n"},"vendor_payment_details":{"type":"object","nullable":true,"description":"Vendor's payment details for the end-user to send fiat to.\nPresent only for on-ramp orders at `awaiting_fiat_transfer` status and beyond.\n","properties":{"payment_method_name":{"type":"string"},"payment_network_name":{"type":"string"},"details":{"type":"object","description":"Payment method-specific fields (e.g. account_number, phone)"}}},"payment_request":{"type":"boolean","nullable":true,"description":"`true` if the vendor uses payment request flow — the partner must call\n`submit-payer-details` before `confirm-payment-sent`.\n"},"send_out_tx_hash":{"type":"string","nullable":true,"description":"Destination-chain transaction hash after a send-out completes.\nPresent only for on-ramp orders with `send_out_completed` status.\n"},"recipient_context":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientContext"}],"nullable":true,"description":"Immutable third-party recipient context; absent for self-payout and on-ramp."},"compliance":{"allOf":[{"$ref":"#/components/schemas/PartnerComplianceCase"}],"nullable":true,"description":"Current durable compliance-case state for a third-party payout."}}},"PartnerOrderStatus":{"type":"string","description":"Order status.\n\n**Off-ramp statuses (crypto → fiat):**\n- `created` — order created, quote locked\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_crypto_transfer_authorization` — partner must authorize crypto transfer via EIP-712 signature\n- `crypto_transfer_authorization_pending` — authorization submitted, awaiting on-chain confirmation\n- `crypto_received` — crypto locked in escrow\n- `fiat_payment_started` — buyer submitted fiat payment proof\n- `awaiting_fiat_received_confirmation` — waiting for partner to confirm fiat receipt\n- `fiat_received_confirmed` — fiat confirmed, crypto release pending\n- `crypto_release_pending` — escrow release in progress\n- `completed` — order fully completed\n- `cancelled` — order cancelled\n- `failed` — terminal error\n- `price_changed_requote_needed` — slippage exceeded at initiation (not a trade status)\n\n**On-ramp statuses (fiat → crypto):**\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_vendor_escrow_funding` — vendor accepted, funding escrow with crypto\n- `awaiting_fiat_transfer` — crypto locked in escrow, partner must confirm payment sent\n- `fiat_transfer_pending` — fiat payment confirmed, awaiting vendor confirmation of receipt\n- `completed` — vendor confirmed fiat received, crypto released to end-user wallet\n- `cancelled` — order cancelled\n- `failed` — escrow error\n\n**On-ramp send-out statuses (optional post-trade cross-chain transfer):**\n\n> After an on-ramp order reaches `completed`, the partner may optionally initiate a\n> **send-out** — a cross-chain transfer of the received crypto to a destination network\n> (e.g. XAI → Arbitrum). This is an additional flow and does not affect the core trade lifecycle.\n> The `completed` status reflects that the trade itself is done; send-out statuses track\n> the subsequent cross-chain transfer only.\n\n- `send_out_pending` — send-out initiated, cross-chain transfer in progress\n- `send_out_completed` — cross-chain transfer successfully delivered to destination\n- `send_out_failed` — cross-chain transfer failed; the on-ramp trade itself remains completed\n","enum":["created","awaiting_liquidity_provider","awaiting_crypto_transfer_authorization","crypto_transfer_authorization_pending","crypto_received","fiat_payment_started","awaiting_fiat_received_confirmation","fiat_received_confirmed","crypto_release_pending","awaiting_vendor_escrow_funding","awaiting_fiat_transfer","fiat_transfer_pending","completed","cancelled","failed","price_changed_requote_needed","send_out_pending","send_out_completed","send_out_failed"]},"PartnerOrderAmounts":{"type":"object","properties":{"crypto_amount":{"type":"string","description":"Total crypto amount (standard units) — the fee-exclusive gross: the buy/sell amount plus the\nplatform fee. Partner-fee-exclusive, except for a crypto-anchored onramp order (originally\nquoted with `crypto_amount`): there the amount was sized up so the delivered amount stays\nexact after the partner fee is withheld, so this field includes the markup. A crypto-anchored\nofframp order carves both fees out of the pinned deposit instead, so this field and\n`fee_breakdown.platform_fee` both shrink relative to a no-markup request, even though the\nmarkup itself stays out of this field. Every other case is unaffected — the markup is\nreported separately in `fee_breakdown.partner_fee`.\n\nOn-ramp — what the end user receives: `crypto_amount − fee_breakdown.platform_fee −\nfee_breakdown.partner_fee` for a fiat-anchored order, or exactly this `crypto_amount` for a\ncrypto-anchored order (both fees are withheld at bridge send-out without changing this\nfield). Off-ramp — what the partner funds at transfer authorization:\n`crypto_amount + fee_breakdown.partner_fee`.\n"},"fiat_amount":{"type":"string","description":"Fiat amount the user receives"},"crypto_currency":{"type":"string","description":"Cryptocurrency code"},"fiat_currency":{"type":"string","description":"Fiat currency code"},"rate":{"type":"string","description":"Crypto-to-fiat exchange rate"}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}},"TimelineEntry":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"timestamp":{"type":"string","format":"date-time","description":"When this status was reached"},"description":{"type":"string","description":"Human-readable description of the status change"}}},"PartnerRecipientContext":{"type":"object","required":["sender_id","recipient_id","recipient_destination_id","sender_recipient_relationship","purpose_of_payment","screening_status"],"properties":{"sender_id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"recipient_destination_id":{"type":"string","format":"uuid"},"sender_recipient_relationship":{"type":"string"},"purpose_of_payment":{"type":"string"},"purpose_details":{"type":"string"},"screening_status":{"type":"string"}}},"PartnerComplianceCase":{"type":"object","required":["case_id","status","risk_decision","requires_review"],"description":"The compliance record for a third-party payout. In v1 the controls run BEFORE the order is\ncreated, so a payout that fails one is rejected with an error rather than returned as a held\norder — see the error codes in the Third-party payouts guide.\n","properties":{"case_id":{"type":"string","format":"uuid","description":"Identifier of the compliance record — the payout's `quote_id`, so a partner and a Unigox\noperator refer to the same record.\n"},"status":{"type":"string","enum":["authorized"],"description":"v1 emits `authorized`; a rejected payout returns an error instead."},"risk_decision":{"type":"string","enum":["allow"],"description":"v1 emits `allow`; a blocking decision surfaces as an error, not a status."},"requires_review":{"type":"boolean","description":"Always false in v1. Operator review happens Unigox-side over the same record."}}},"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/orders/{order_id}":{"get":{"tags":["Orders"],"summary":"Order details","description":"Get the current status, amounts, and full timeline for a specific order.\nThe `next_action` field indicates what the partner **must** do to advance the order.\n`null` means no action is required from the partner at this stage — the order is waiting\nfor the counterparty (e.g. the buyer to submit fiat payment proof).\n\nThe `allowed_actions` array lists all API actions the partner **can** perform at the current\nstatus. An action can be in `allowed_actions` even when `next_action` is `null` — this means\nthe action is optional. For example, `confirm-fiat-received` is available at `crypto_received`\nas a fast-track: if you already know the fiat has arrived (via your own systems), you can\nconfirm immediately without waiting for the buyer's payment proof flow to complete.\n\n**Order flow summary:**\n\n| Status | next_action | Who acts |\n|---|---|---|\n| `created` | — | Partner (or wait for vendor match) |\n| `awaiting_crypto_transfer_authorization` | `authorize_crypto_transfer` | **Partner** |\n| `crypto_transfer_authorization_pending` | — | System (on-chain confirmation) |\n| `crypto_received` | — | **Buyer** submits fiat payment proof |\n| `fiat_payment_started` | — | System reviews proof |\n| `fiat_payment_review_started` | — | System accepts/rejects proof |\n| `awaiting_fiat_received_confirmation` | `confirm_fiat_received` | **Partner** |\n| `completed` | — | — |\n\n**Fiat settlement and confirmation:**\n\nYou can call `confirm-fiat-received` at any time after crypto is in escrow.\nYou do not need to wait for a system notification. If you know through your\nown systems that the recipient has received the fiat payment, confirm immediately.\n\nSome liquidity providers also send automatic settlement confirmations when the\nrecipient receives the fiat. When the order's `has_fiat_settlement_notification`\nis `true` (see the quote/order response), the order moves to `completed` on its own\nand you receive the `status=completed` webhook — no manual confirm needed. If you\nalready confirmed manually, the auto-confirmation is a no-op; if the auto-confirmation\narrives first, your manual confirm call is also a no-op. Both paths are safe. See the\nWebhooks page → \"Settlement notifications\" for details.\n\n**How to implement:**\n- Always implement a `confirm-fiat-received` call in your flow.\n- Listen for webhooks. If you receive `status=completed`, no further action needed.\n- If `next_action=confirm_fiat_received`, call `confirm-fiat-received` when you\n  know fiat has been received.\n- Do not wait for `next_action` if you already have confirmation from your\n  own banking or payment systems.\n\n**Possible statuses:** `created`, `awaiting_liquidity_provider`, `awaiting_crypto_transfer_authorization`, `crypto_transfer_authorization_pending`, `crypto_received`, `fiat_payment_started`, `awaiting_fiat_received_confirmation`, `fiat_received_confirmed`, `crypto_release_pending`, `completed`, `cancelled`, `failed`, `price_changed_requote_needed`.\n","operationId":"getOrder","parameters":[{"name":"order_id","in":"path","required":true,"description":"Order UUID returned by the initiate endpoint","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Order retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PartnerOrderResponse"}}}]}}}},"401":{"description":"Unauthorized — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"404":{"description":"Order not found or does not belong to this partner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}}}
```

## Get transfer authorization parameters

> Returns pre-filled parameters required to build and sign the EIP-712 forward request.\
> Use this endpoint first, then sign locally and send the signature to\
> \`/api/v1/partner/orders/{order\_id}/authorize-crypto-transfer\`.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Orders","description":"Order management, status tracking, and lifecycle actions"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/orders/{order_id}/transfer-authorization-parameters":{"get":{"tags":["SDK","Orders"],"summary":"Get transfer authorization parameters","description":"Returns pre-filled parameters required to build and sign the EIP-712 forward request.\nUse this endpoint first, then sign locally and send the signature to\n`/api/v1/partner/orders/{order_id}/authorize-crypto-transfer`.\n","operationId":"getTransferAuthorizationParameters","parameters":[{"name":"order_id","in":"path","required":true,"description":"Order UUID","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Transfer authorization parameters returned","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"order_id":{"type":"string","format":"uuid"},"network_preset":{"type":"string"},"chain_id":{"type":"string"},"rpc_url":{"type":"string","description":"JSON-RPC URL for the target chain"},"token_address":{"type":"string"},"forwarder_address":{"type":"string"},"recipient_address":{"type":"string","description":"Escrow address to receive the crypto"},"sender_address":{"type":"string","description":"Partner wallet address (must match the signing private key)"},"amount_atomic":{"type":"string"},"amount_human":{"type":"string"},"crypto_currency":{"type":"string"},"crypto_decimals":{"type":"integer"},"recommended_gas_limit":{"type":"string"},"recommended_ttl_seconds":{"type":"integer"},"transfer_data":{"type":"string","description":"Pre-encoded ERC20 transfer(address,uint256) calldata (hex with 0x prefix)"},"forwarder_nonce":{"type":"string","description":"Current forwarder nonce for the sender address (used in EIP-712 signing)"},"authorization_path":{"type":"string"}}}}}]}}}},"401":{"description":"Unauthorized"},"404":{"description":"No order with this id belongs to your partner account."},"409":{"description":"The order exists but is not at a stage where it can be funded. A new order starts at `awaiting_liquidity_provider` with no escrow yet, so this is the normal answer until a provider accepts — poll `GET /orders/{order_id}` until `next_action` becomes `authorize_crypto_transfer`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}}}
```

## Authorize crypto transfer

> Submit signed EIP-712 \`forward\_request\` to authorize transfer of order crypto to escrow.\
> \
> \*\*Security note:\*\* Your private key is never sent to the API. The SDK uses it locally to\
> construct and sign the EIP-712 ForwardRequest on your machine. Only the resulting signature\
> and the unsigned forward\_request fields are submitted. You can verify this by reading the\
> SDK source code.\
> \
> Recommended: use SDK helper files (only 4 arguments needed):\
> \- Python: \`<https://github.com/Unigox/sdk\\_python/blob/main/partner\\_authorize\\_crypto\\_transfer\\_sdk.py\\`\\>
> \- JavaScript: \`<https://github.com/Unigox/sdk\\_javascript/blob/main/partner\\_authorize\\_crypto\\_transfer\\_sdk.js\\`\\>
> \
> The SDK automatically fetches all required parameters (chain, token, forwarder, escrow\
> address, RPC URL) from the \`transfer-authorization-parameters\` endpoint.\
> \
> SDK helper options:\
> \- End-to-end one call: \`authorize\_crypto\_transfer(...)\` / \`authorizeCryptoTransfer(...)\`\
> \- Build unsigned request only: \`build\_forward\_request(...)\` / \`buildForwardRequest(...)\`\
> \- Sign existing request only: \`sign\_forward\_request(...)\` / \`signForwardRequest(...)\`\
> \- Build full request body \`{forward\_request, signature}\`:\
> &#x20; \`build\_authorize\_crypto\_transfer\_payload(...)\` / \`buildAuthorizeCryptoTransferPayload(...)\`\
> \
> Manual implementation: call \`GET /transfer-authorization-parameters\` first, then build\
> the EIP-712 typed data and sign with your partner wallet private key locally.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Orders","description":"Order management, status tracking, and lifecycle actions"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/orders/{order_id}/authorize-crypto-transfer":{"post":{"tags":["SDK","Orders"],"summary":"Authorize crypto transfer","description":"Submit signed EIP-712 `forward_request` to authorize transfer of order crypto to escrow.\n\n**Security note:** Your private key is never sent to the API. The SDK uses it locally to\nconstruct and sign the EIP-712 ForwardRequest on your machine. Only the resulting signature\nand the unsigned forward_request fields are submitted. You can verify this by reading the\nSDK source code.\n\nRecommended: use SDK helper files (only 4 arguments needed):\n- Python: `https://github.com/Unigox/sdk_python/blob/main/partner_authorize_crypto_transfer_sdk.py`\n- JavaScript: `https://github.com/Unigox/sdk_javascript/blob/main/partner_authorize_crypto_transfer_sdk.js`\n\nThe SDK automatically fetches all required parameters (chain, token, forwarder, escrow\naddress, RPC URL) from the `transfer-authorization-parameters` endpoint.\n\nSDK helper options:\n- End-to-end one call: `authorize_crypto_transfer(...)` / `authorizeCryptoTransfer(...)`\n- Build unsigned request only: `build_forward_request(...)` / `buildForwardRequest(...)`\n- Sign existing request only: `sign_forward_request(...)` / `signForwardRequest(...)`\n- Build full request body `{forward_request, signature}`:\n  `build_authorize_crypto_transfer_payload(...)` / `buildAuthorizeCryptoTransferPayload(...)`\n\nManual implementation: call `GET /transfer-authorization-parameters` first, then build\nthe EIP-712 typed data and sign with your partner wallet private key locally.\n","operationId":"authorizeCryptoTransfer","parameters":[{"name":"order_id","in":"path","required":true,"description":"Order UUID","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["forward_request","signature"],"properties":{"forward_request":{"type":"object","description":"Signed EIP-712 ForwardRequest payload"},"signature":{"type":"string","description":"Hex-encoded signature from partner wallet"}}}}}},"responses":{"200":{"description":"Crypto authorization accepted"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"404":{"description":"Order not found"},"409":{"description":"Order is not in the correct status for crypto authorization"},"422":{"description":"Insufficient token balance — the partner wallet does not hold enough\ntokens to fund the escrow. Top up the wallet and retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}},"502":{"description":"Transactor service error (e.g. on-chain simulation failed)"}}}}}}
```

## Confirm fiat received

> Confirm that the recipient has received the fiat payment for an off-ramp order.\
> This triggers the crypto release from escrow.\
> \
> Call this endpoint as soon as you know fiat has been received. You do not need\
> to wait for a system prompt. If you have confirmation from your own banking or\
> payment systems, confirm immediately.\
> \
> This endpoint is idempotent. If the order already completed (via automatic\
> settlement confirmation from the liquidity provider), calling this returns\
> the current order status with no side effects.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Orders","description":"Order management, status tracking, and lifecycle actions"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}}},"paths":{"/api/v1/partner/orders/{order_id}/confirm-fiat-received":{"post":{"tags":["Orders"],"summary":"Confirm fiat received","description":"Confirm that the recipient has received the fiat payment for an off-ramp order.\nThis triggers the crypto release from escrow.\n\nCall this endpoint as soon as you know fiat has been received. You do not need\nto wait for a system prompt. If you have confirmation from your own banking or\npayment systems, confirm immediately.\n\nThis endpoint is idempotent. If the order already completed (via automatic\nsettlement confirmation from the liquidity provider), calling this returns\nthe current order status with no side effects.\n","operationId":"confirmFiatReceived","parameters":[{"name":"order_id","in":"path","required":true,"description":"Order UUID","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Fiat receipt confirmed successfully"},"400":{"description":"Bad Request"},"401":{"description":"Unauthorized"},"404":{"description":"Order not found"}}}}}}
```

## Cancel order

> Cancel an order if cancellation is allowed at the current status.\
> Check \`allowed\_actions\` on the order response — \`cancel\` is available when cancellation is permitted.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Orders","description":"Order management, status tracking, and lifecycle actions"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}}},"paths":{"/api/v1/partner/orders/{order_id}/cancel":{"post":{"tags":["Orders"],"summary":"Cancel order","description":"Cancel an order if cancellation is allowed at the current status.\nCheck `allowed_actions` on the order response — `cancel` is available when cancellation is permitted.\n","operationId":"cancelOrder","parameters":[{"name":"order_id","in":"path","required":true,"description":"Order UUID","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Order cancelled successfully"},"400":{"description":"Bad Request — cancellation not allowed at current status"},"401":{"description":"Unauthorized"},"404":{"description":"Order not found"}}}}}}
```

## List orders

> Get a paginated list of orders with optional filtering by status.\
> Each order includes \`allowed\_actions\` indicating which API actions are available.<br>

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Orders","description":"Order management, status tracking, and lifecycle actions"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PartnerOrderResponse":{"type":"object","properties":{"order_id":{"type":"string","format":"uuid","description":"Unique order identifier"},"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"next_action":{"type":"string","nullable":true,"description":"The action the partner **must** perform to advance the order. `null` means\nno action is required from the partner at this stage — the order is waiting\nfor the counterparty or for an automated system step.\n\nPossible values:\n- `authorize_crypto_transfer` — partner must authorize escrow funding\n- `confirm_fiat_received` — partner must confirm they received the fiat payment\n- `request_new_quote` — price changed, partner must request a new quote\n- `update_kyc_address` — address fields are required for EUR/AUD/GBP payouts; see `action_required` for the endpoint and fields to submit\n- `null` — no required action (waiting for buyer or system)\n","enum":["authorize_crypto_transfer","confirm_fiat_received","request_new_quote","update_kyc_address",null]},"action_required":{"type":"object","nullable":true,"description":"Present when the order needs partner intervention before it can proceed.\nContains machine-readable type, human-readable message, the API endpoint to call,\nand the fields to provide. Null/omitted when no action is needed.\n","required":["type","message"],"properties":{"type":{"type":"string","description":"Machine-readable action identifier"},"message":{"type":"string","description":"Human-readable explanation of what is needed and why"},"endpoint":{"type":"string","description":"API endpoint to call to resolve this action"},"fields":{"type":"array","description":"Fields the partner needs to provide","items":{"type":"object","required":["field","label"],"properties":{"field":{"type":"string","description":"API field name"},"label":{"type":"string","description":"Human-readable description of the field"}}}}}},"allowed_actions":{"type":"array","items":{"type":"string"},"description":"List of API actions the partner **can** perform at the current status. An action\nmay be present here even when `next_action` is `null` — meaning it is optional.\n\nNotable case: `confirm-fiat-received` appears at both `crypto_received` and\n`awaiting_fiat_received_confirmation`. At `crypto_received` it is a **fast-track**:\nif you already know the fiat has arrived via your own systems, you can confirm\nimmediately. Otherwise, wait for the buyer's proof flow to complete — the order\nwill reach `awaiting_fiat_received_confirmation` automatically.\n\nStatus-to-actions mapping:\n- `created` → `[\"cancel\"]`\n- `awaiting_liquidity_provider` → `[\"cancel\"]`\n- `awaiting_crypto_transfer_authorization` → `[\"authorize-crypto-transfer\", \"cancel\"]`\n- `crypto_transfer_authorization_pending` → `[\"cancel\"]`\n- `crypto_received` → `[\"confirm-fiat-received\"]` (optional fast-track)\n- `fiat_payment_started` → `[\"confirm-fiat-received\"]` (optional fast-track after buyer proof submission)\n- `awaiting_fiat_received_confirmation` → `[\"confirm-fiat-received\"]` (required)\n- `fiat_received_confirmed`, `crypto_release_pending`, `completed`, `cancelled`, `failed` → `[]`\n"},"crypto_transfer_authorization_seconds_left":{"type":"integer","format":"int64","nullable":true,"description":"Remaining seconds to authorize crypto transfer while order is in\n`awaiting_crypto_transfer_authorization` / `crypto_transfer_authorization_pending`.\n`null` for all other statuses.\n"},"amounts":{"$ref":"#/components/schemas/PartnerOrderAmounts"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/TimelineEntry"},"description":"Chronological list of status changes"},"has_fiat_settlement_notification":{"type":"boolean","description":"Whether the matched liquidity provider sends fiat settlement notifications.\nWhen `true`, the order will complete automatically when fiat is delivered.\nWhen `false`, you must call `confirm-fiat-received`. You can call\n`confirm-fiat-received` at any time regardless of this value.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"order_type":{"type":"string","nullable":true,"description":"Order type. Present for on-ramp orders. Absent for off-ramp orders.\n- `onramp` — buy crypto with fiat\n"},"vendor_payment_details":{"type":"object","nullable":true,"description":"Vendor's payment details for the end-user to send fiat to.\nPresent only for on-ramp orders at `awaiting_fiat_transfer` status and beyond.\n","properties":{"payment_method_name":{"type":"string"},"payment_network_name":{"type":"string"},"details":{"type":"object","description":"Payment method-specific fields (e.g. account_number, phone)"}}},"payment_request":{"type":"boolean","nullable":true,"description":"`true` if the vendor uses payment request flow — the partner must call\n`submit-payer-details` before `confirm-payment-sent`.\n"},"send_out_tx_hash":{"type":"string","nullable":true,"description":"Destination-chain transaction hash after a send-out completes.\nPresent only for on-ramp orders with `send_out_completed` status.\n"},"recipient_context":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientContext"}],"nullable":true,"description":"Immutable third-party recipient context; absent for self-payout and on-ramp."},"compliance":{"allOf":[{"$ref":"#/components/schemas/PartnerComplianceCase"}],"nullable":true,"description":"Current durable compliance-case state for a third-party payout."}}},"PartnerOrderStatus":{"type":"string","description":"Order status.\n\n**Off-ramp statuses (crypto → fiat):**\n- `created` — order created, quote locked\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_crypto_transfer_authorization` — partner must authorize crypto transfer via EIP-712 signature\n- `crypto_transfer_authorization_pending` — authorization submitted, awaiting on-chain confirmation\n- `crypto_received` — crypto locked in escrow\n- `fiat_payment_started` — buyer submitted fiat payment proof\n- `awaiting_fiat_received_confirmation` — waiting for partner to confirm fiat receipt\n- `fiat_received_confirmed` — fiat confirmed, crypto release pending\n- `crypto_release_pending` — escrow release in progress\n- `completed` — order fully completed\n- `cancelled` — order cancelled\n- `failed` — terminal error\n- `price_changed_requote_needed` — slippage exceeded at initiation (not a trade status)\n\n**On-ramp statuses (fiat → crypto):**\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_vendor_escrow_funding` — vendor accepted, funding escrow with crypto\n- `awaiting_fiat_transfer` — crypto locked in escrow, partner must confirm payment sent\n- `fiat_transfer_pending` — fiat payment confirmed, awaiting vendor confirmation of receipt\n- `completed` — vendor confirmed fiat received, crypto released to end-user wallet\n- `cancelled` — order cancelled\n- `failed` — escrow error\n\n**On-ramp send-out statuses (optional post-trade cross-chain transfer):**\n\n> After an on-ramp order reaches `completed`, the partner may optionally initiate a\n> **send-out** — a cross-chain transfer of the received crypto to a destination network\n> (e.g. XAI → Arbitrum). This is an additional flow and does not affect the core trade lifecycle.\n> The `completed` status reflects that the trade itself is done; send-out statuses track\n> the subsequent cross-chain transfer only.\n\n- `send_out_pending` — send-out initiated, cross-chain transfer in progress\n- `send_out_completed` — cross-chain transfer successfully delivered to destination\n- `send_out_failed` — cross-chain transfer failed; the on-ramp trade itself remains completed\n","enum":["created","awaiting_liquidity_provider","awaiting_crypto_transfer_authorization","crypto_transfer_authorization_pending","crypto_received","fiat_payment_started","awaiting_fiat_received_confirmation","fiat_received_confirmed","crypto_release_pending","awaiting_vendor_escrow_funding","awaiting_fiat_transfer","fiat_transfer_pending","completed","cancelled","failed","price_changed_requote_needed","send_out_pending","send_out_completed","send_out_failed"]},"PartnerOrderAmounts":{"type":"object","properties":{"crypto_amount":{"type":"string","description":"Total crypto amount (standard units) — the fee-exclusive gross: the buy/sell amount plus the\nplatform fee. Partner-fee-exclusive, except for a crypto-anchored onramp order (originally\nquoted with `crypto_amount`): there the amount was sized up so the delivered amount stays\nexact after the partner fee is withheld, so this field includes the markup. A crypto-anchored\nofframp order carves both fees out of the pinned deposit instead, so this field and\n`fee_breakdown.platform_fee` both shrink relative to a no-markup request, even though the\nmarkup itself stays out of this field. Every other case is unaffected — the markup is\nreported separately in `fee_breakdown.partner_fee`.\n\nOn-ramp — what the end user receives: `crypto_amount − fee_breakdown.platform_fee −\nfee_breakdown.partner_fee` for a fiat-anchored order, or exactly this `crypto_amount` for a\ncrypto-anchored order (both fees are withheld at bridge send-out without changing this\nfield). Off-ramp — what the partner funds at transfer authorization:\n`crypto_amount + fee_breakdown.partner_fee`.\n"},"fiat_amount":{"type":"string","description":"Fiat amount the user receives"},"crypto_currency":{"type":"string","description":"Cryptocurrency code"},"fiat_currency":{"type":"string","description":"Fiat currency code"},"rate":{"type":"string","description":"Crypto-to-fiat exchange rate"}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}},"TimelineEntry":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"timestamp":{"type":"string","format":"date-time","description":"When this status was reached"},"description":{"type":"string","description":"Human-readable description of the status change"}}},"PartnerRecipientContext":{"type":"object","required":["sender_id","recipient_id","recipient_destination_id","sender_recipient_relationship","purpose_of_payment","screening_status"],"properties":{"sender_id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"recipient_destination_id":{"type":"string","format":"uuid"},"sender_recipient_relationship":{"type":"string"},"purpose_of_payment":{"type":"string"},"purpose_details":{"type":"string"},"screening_status":{"type":"string"}}},"PartnerComplianceCase":{"type":"object","required":["case_id","status","risk_decision","requires_review"],"description":"The compliance record for a third-party payout. In v1 the controls run BEFORE the order is\ncreated, so a payout that fails one is rejected with an error rather than returned as a held\norder — see the error codes in the Third-party payouts guide.\n","properties":{"case_id":{"type":"string","format":"uuid","description":"Identifier of the compliance record — the payout's `quote_id`, so a partner and a Unigox\noperator refer to the same record.\n"},"status":{"type":"string","enum":["authorized"],"description":"v1 emits `authorized`; a rejected payout returns an error instead."},"risk_decision":{"type":"string","enum":["allow"],"description":"v1 emits `allow`; a blocking decision surfaces as an error, not a status."},"requires_review":{"type":"boolean","description":"Always false in v1. Operator review happens Unigox-side over the same record."}}},"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}},"paths":{"/api/v1/partner/orders":{"get":{"tags":["Orders"],"summary":"List orders","description":"Get a paginated list of orders with optional filtering by status.\nEach order includes `allowed_actions` indicating which API actions are available.\n","operationId":"listOrders","parameters":[{"name":"status","in":"query","required":false,"description":"Filter by partner order status","schema":{"type":"string","enum":["created","awaiting_liquidity_provider","awaiting_crypto_transfer_authorization","crypto_transfer_authorization_pending","crypto_received","awaiting_fiat_received_confirmation","fiat_received_confirmed","crypto_release_pending","completed","cancelled","failed","price_changed_requote_needed"]}},{"name":"page","in":"query","required":false,"description":"Page number (default 1)","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","required":false,"description":"Results per page (default 20, max 100)","schema":{"type":"integer","default":20,"maximum":100}}],"responses":{"200":{"description":"Orders retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/PartnerOrderResponse"}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total_count":{"type":"integer"},"total_pages":{"type":"integer"}}}}}}}]}}}},"401":{"description":"Unauthorized — invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerErrorResponse"}}}}}}}}}
```


# Webhooks

Real-time notifications for order and KYC status changes. The system sends a signed HTTP POST to your configured webhook URL on every relevant event.

**Event model**

Two event types are delivered:

| Event type             | Fired when                                        |
| ---------------------- | ------------------------------------------------- |
| `order.status.changed` | An order transitions to a new status              |
| `user.kyc.updated`     | A user's KYC verification is approved or declined |

All events share the same envelope (`event_id`, `event_type`, `created_at`, `data`) and the same signature scheme. The structure of the `data` object differs per event type.

**Payload shape — `order.status.changed`**

```json
{
  "event_id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "event_type": "order.status.changed",
  "created_at": "2026-02-01T12:00:05Z",
  "data": {
    "order_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
    "status": "crypto_received",
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "crypto_amount": "101.5",
    "crypto_currency": "USDT",
    "partner_fee": "1",
    "partner_fee_pct": 1,
    "fiat_amount": "152880.00",
    "fiat_currency": "NGN",
    "provider": "p2p",
    "payment_details_id": "987"
  }
}
```

**Payload shape — `user.kyc.updated`**

Fired when a user's KYC status changes — to `verified`, `rejected`, or to a manual compliance review hold (`UNDER_REVIEW`). Use this event to unlock order creation for the user on your side (only on `VERIFIED`; `UNDER_REVIEW` is not cleared).

```json
{
  "event_id": "evt_2d8418dd-e3a8-4463-a11e-6b54f354ca90",
  "event_type": "user.kyc.updated",
  "created_at": "2026-03-10T11:37:42Z",
  "data": {
    "user_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "kyc_status": "VERIFIED"
  }
}
```

When KYC is declined, `rejection_reasons` is included:

```json
{
  "event_id": "evt_fc75484d-b374-4d05-b54e-820f3dd80e6d",
  "event_type": "user.kyc.updated",
  "created_at": "2026-03-10T12:00:00Z",
  "data": {
    "user_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "kyc_status": "REJECTED",
    "rejection_reasons": ["Document expired", "Photo quality low"]
  }
}
```

When a customer is placed on a manual compliance review hold, `kyc_status` is `UNDER_REVIEW`:

```json
{
  "event_id": "evt_9a1c7f20-4d3b-4e88-9f2a-1b6d0c5e7a3f",
  "event_type": "user.kyc.updated",
  "created_at": "2026-03-10T12:05:00Z",
  "data": {
    "user_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "kyc_status": "UNDER_REVIEW"
  }
}
```

`kyc_status` values: `VERIFIED` | `REJECTED` | `UNDER_REVIEW`

`UNDER_REVIEW` is a manual compliance review: the customer is NOT cleared and can take up to 24 hours (unlike an automated check of a couple of minutes). Do not unlock order creation until `VERIFIED`.

**Signature verification**

Every delivery includes two headers for HMAC-SHA256 verification:

* `X-Unigox-Signature: sha256=<hex>` — HMAC of `"<timestamp>.<raw_body>"`
* `X-Unigox-Timestamp: <unix_seconds>`

Verification pseudocode:

```
expected = HMAC-SHA256(webhook_secret, "<timestamp>.<raw_body>")
valid    = constant_time_compare(expected, signature_from_header)
```

**Delivery guarantees**

* At-least-once delivery. Your endpoint may receive the same event more than once.
* De-duplicate by `event_id`.
* Respond with any 2xx status within 10 seconds to acknowledge receipt.

**Retry policy**

Failed deliveries (non-2xx or timeout) are retried with exponential backoff: 1 min → 5 min → 15 min → 1 h → 6 h → 12 h → 24 h (max 10 attempts over \~3.5 days).

**Partner-facing statuses** (`order.status.changed`)

| Status                                   | Description                                                      |
| ---------------------------------------- | ---------------------------------------------------------------- |
| `created`                                | Order created                                                    |
| `awaiting_liquidity_provider`            | Waiting for vendor match                                         |
| `awaiting_crypto_transfer_authorization` | Partner must authorize crypto transfer                           |
| `crypto_received`                        | Crypto received in escrow                                        |
| `fiat_payment_started`                   | Buyer submitted fiat payment proof                               |
| `fiat_payment_review_started`            | Payment proof under admin review                                 |
| `awaiting_fiat_received_confirmation`    | Proof approved, awaiting seller confirmation                     |
| `completed`                              | Order completed, crypto released                                 |
| `cancelled`                              | Order cancelled or payment declined                              |
| `failed`                                 | Escrow error, or fiat-anchored safety bound exceeded (see below) |
| `dispute_started`                        | Dispute opened                                                   |

**Fiat-anchored safety bound failure**

For fiat-anchored off-ramp orders (`anchor_type: "fiat"`), if the crypto cost we quote exceeds 1.20× the expected amount at the time of settlement, the order moves to `failed` status asynchronously (not during `/initiate`). The deposit is automatically refunded from escrow back to the partner wallet. Create a new quote to retry the trade flow.

**Settlement notifications (recipient received the fiat)**

Some liquidity providers automatically confirm when the fiat payout reaches the recipient's bank account or mobile wallet. When the provider matched to an order supports this, the order moves to `completed` on its own and you receive an `order.status.changed` webhook with `status=completed` — that webhook is your signal that the recipient has actually received the money.

Whether the matched provider sends this is reported per order by the `has_fiat_settlement_notification` field on the **quote** and **order** responses:

* `true` — the matched provider sends an automatic settlement confirmation. You will receive the `completed` webhook once fiat is delivered; no manual action is needed.
* `false` — no automatic confirmation for this order. Confirm delivery yourself by calling `confirm-fiat-received` (or rely on your own banking/payment systems).

To match **only** providers that send settlement notifications, pass `has_fiat_settlement_notification: true` in the quote request — providers that can't offer it are then excluded from matching.

Availability is provider- and corridor-dependent: not every currency has a provider that sends settlement notifications, so always read the flag on each quote rather than assuming it. Manual `confirm-fiat-received` and the automatic confirmation are mutually idempotent — whichever happens first wins and the other is a safe no-op.

## Get webhook config

> Returns the current webhook configuration for your partner account,\
> including the URL and whether the webhook is enabled.<br>

````json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Webhooks","description":"Real-time notifications for order and KYC status changes.\nThe system sends a signed HTTP POST to your configured webhook URL on every relevant event.\n\n**Event model**\n\nTwo event types are delivered:\n\n| Event type | Fired when |\n|---|---|\n| `order.status.changed` | An order transitions to a new status |\n| `user.kyc.updated` | A user's KYC verification is approved or declined |\n\nAll events share the same envelope (`event_id`, `event_type`, `created_at`, `data`)\nand the same signature scheme. The structure of the `data` object differs per event type.\n\n**Payload shape — `order.status.changed`**\n\n```json\n{\n  \"event_id\": \"evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n  \"event_type\": \"order.status.changed\",\n  \"created_at\": \"2026-02-01T12:00:05Z\",\n  \"data\": {\n    \"order_id\": \"b2c3d4e5-f6a7-8901-bcde-f12345678901\",\n    \"status\": \"crypto_received\",\n    \"user_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"crypto_amount\": \"101.5\",\n    \"crypto_currency\": \"USDT\",\n    \"partner_fee\": \"1\",\n    \"partner_fee_pct\": 1,\n    \"fiat_amount\": \"152880.00\",\n    \"fiat_currency\": \"NGN\",\n    \"provider\": \"p2p\",\n    \"payment_details_id\": \"987\"\n  }\n}\n```\n\n**Payload shape — `user.kyc.updated`**\n\nFired when a user's KYC status changes — to `verified`, `rejected`, or to a manual compliance\nreview hold (`UNDER_REVIEW`). Use this event to unlock order creation for the user on your side\n(only on `VERIFIED`; `UNDER_REVIEW` is not cleared).\n\n```json\n{\n  \"event_id\": \"evt_2d8418dd-e3a8-4463-a11e-6b54f354ca90\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T11:37:42Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"VERIFIED\"\n  }\n}\n```\n\nWhen KYC is declined, `rejection_reasons` is included:\n\n```json\n{\n  \"event_id\": \"evt_fc75484d-b374-4d05-b54e-820f3dd80e6d\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:00:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"REJECTED\",\n    \"rejection_reasons\": [\"Document expired\", \"Photo quality low\"]\n  }\n}\n```\n\nWhen a customer is placed on a manual compliance review hold, `kyc_status` is `UNDER_REVIEW`:\n\n```json\n{\n  \"event_id\": \"evt_9a1c7f20-4d3b-4e88-9f2a-1b6d0c5e7a3f\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:05:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"UNDER_REVIEW\"\n  }\n}\n```\n\n`kyc_status` values: `VERIFIED` | `REJECTED` | `UNDER_REVIEW`\n\n`UNDER_REVIEW` is a manual compliance review: the customer is NOT cleared and can take up to\n24 hours (unlike an automated check of a couple of minutes). Do not unlock order creation until\n`VERIFIED`.\n\n**Signature verification**\n\nEvery delivery includes two headers for HMAC-SHA256 verification:\n- `X-Unigox-Signature: sha256=<hex>` — HMAC of `\"<timestamp>.<raw_body>\"`\n- `X-Unigox-Timestamp: <unix_seconds>`\n\nVerification pseudocode:\n```\nexpected = HMAC-SHA256(webhook_secret, \"<timestamp>.<raw_body>\")\nvalid    = constant_time_compare(expected, signature_from_header)\n```\n\n**Delivery guarantees**\n\n- At-least-once delivery. Your endpoint may receive the same event more than once.\n- De-duplicate by `event_id`.\n- Respond with any 2xx status within 10 seconds to acknowledge receipt.\n\n**Retry policy**\n\nFailed deliveries (non-2xx or timeout) are retried with exponential backoff:\n1 min → 5 min → 15 min → 1 h → 6 h → 12 h → 24 h (max 10 attempts over ~3.5 days).\n\n**Partner-facing statuses** (`order.status.changed`)\n\n| Status | Description |\n|---|---|\n| `created` | Order created |\n| `awaiting_liquidity_provider` | Waiting for vendor match |\n| `awaiting_crypto_transfer_authorization` | Partner must authorize crypto transfer |\n| `crypto_received` | Crypto received in escrow |\n| `fiat_payment_started` | Buyer submitted fiat payment proof |\n| `fiat_payment_review_started` | Payment proof under admin review |\n| `awaiting_fiat_received_confirmation` | Proof approved, awaiting seller confirmation |\n| `completed` | Order completed, crypto released |\n| `cancelled` | Order cancelled or payment declined |\n| `failed` | Escrow error, or fiat-anchored safety bound exceeded (see below) |\n| `dispute_started` | Dispute opened |\n\n**Fiat-anchored safety bound failure**\n\nFor fiat-anchored off-ramp orders (`anchor_type: \"fiat\"`), if the crypto cost we quote exceeds\n1.20× the expected amount at the time of settlement, the order moves to `failed` status asynchronously\n(not during `/initiate`). The deposit is automatically refunded from escrow back to the partner wallet.\nCreate a new quote to retry the trade flow.\n\n**Settlement notifications (recipient received the fiat)**\n\nSome liquidity providers automatically confirm when the fiat payout reaches the\nrecipient's bank account or mobile wallet. When the provider matched to an order\nsupports this, the order moves to `completed` on its own and you receive an\n`order.status.changed` webhook with `status=completed` — that webhook is your signal\nthat the recipient has actually received the money.\n\nWhether the matched provider sends this is reported per order by the\n`has_fiat_settlement_notification` field on the **quote** and **order** responses:\n\n- `true` — the matched provider sends an automatic settlement confirmation. You will\n  receive the `completed` webhook once fiat is delivered; no manual action is needed.\n- `false` — no automatic confirmation for this order. Confirm delivery yourself by\n  calling `confirm-fiat-received` (or rely on your own banking/payment systems).\n\nTo match **only** providers that send settlement notifications, pass\n`has_fiat_settlement_notification: true` in the quote request — providers that can't\noffer it are then excluded from matching.\n\nAvailability is provider- and corridor-dependent: not every currency has a provider\nthat sends settlement notifications, so always read the flag on each quote rather than\nassuming it. Manual `confirm-fiat-received` and the automatic confirmation are mutually\nidempotent — whichever happens first wins and the other is a safe no-op.\n"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"WebhookConfigResponse":{"type":"object","properties":{"url":{"type":"string","nullable":true,"description":"Currently configured webhook URL (null if not set)"},"enabled":{"type":"boolean","description":"Whether webhook delivery is active"}}}}},"paths":{"/api/v1/partner/webhooks":{"get":{"tags":["Webhooks"],"summary":"Get webhook config","description":"Returns the current webhook configuration for your partner account,\nincluding the URL and whether the webhook is enabled.\n","operationId":"getWebhook","responses":{"200":{"description":"Webhook configuration retrieved","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookConfigResponse"}}}]}}}},"401":{"description":"Unauthorized — invalid or missing API key"}}}}}}
````

## Register webhook URL

> Register or update the webhook URL for your partner account. The URL must use HTTPS.\
> Once registered, the webhook is automatically enabled and will start receiving order status events.<br>

````json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Webhooks","description":"Real-time notifications for order and KYC status changes.\nThe system sends a signed HTTP POST to your configured webhook URL on every relevant event.\n\n**Event model**\n\nTwo event types are delivered:\n\n| Event type | Fired when |\n|---|---|\n| `order.status.changed` | An order transitions to a new status |\n| `user.kyc.updated` | A user's KYC verification is approved or declined |\n\nAll events share the same envelope (`event_id`, `event_type`, `created_at`, `data`)\nand the same signature scheme. The structure of the `data` object differs per event type.\n\n**Payload shape — `order.status.changed`**\n\n```json\n{\n  \"event_id\": \"evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n  \"event_type\": \"order.status.changed\",\n  \"created_at\": \"2026-02-01T12:00:05Z\",\n  \"data\": {\n    \"order_id\": \"b2c3d4e5-f6a7-8901-bcde-f12345678901\",\n    \"status\": \"crypto_received\",\n    \"user_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"crypto_amount\": \"101.5\",\n    \"crypto_currency\": \"USDT\",\n    \"partner_fee\": \"1\",\n    \"partner_fee_pct\": 1,\n    \"fiat_amount\": \"152880.00\",\n    \"fiat_currency\": \"NGN\",\n    \"provider\": \"p2p\",\n    \"payment_details_id\": \"987\"\n  }\n}\n```\n\n**Payload shape — `user.kyc.updated`**\n\nFired when a user's KYC status changes — to `verified`, `rejected`, or to a manual compliance\nreview hold (`UNDER_REVIEW`). Use this event to unlock order creation for the user on your side\n(only on `VERIFIED`; `UNDER_REVIEW` is not cleared).\n\n```json\n{\n  \"event_id\": \"evt_2d8418dd-e3a8-4463-a11e-6b54f354ca90\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T11:37:42Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"VERIFIED\"\n  }\n}\n```\n\nWhen KYC is declined, `rejection_reasons` is included:\n\n```json\n{\n  \"event_id\": \"evt_fc75484d-b374-4d05-b54e-820f3dd80e6d\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:00:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"REJECTED\",\n    \"rejection_reasons\": [\"Document expired\", \"Photo quality low\"]\n  }\n}\n```\n\nWhen a customer is placed on a manual compliance review hold, `kyc_status` is `UNDER_REVIEW`:\n\n```json\n{\n  \"event_id\": \"evt_9a1c7f20-4d3b-4e88-9f2a-1b6d0c5e7a3f\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:05:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"UNDER_REVIEW\"\n  }\n}\n```\n\n`kyc_status` values: `VERIFIED` | `REJECTED` | `UNDER_REVIEW`\n\n`UNDER_REVIEW` is a manual compliance review: the customer is NOT cleared and can take up to\n24 hours (unlike an automated check of a couple of minutes). Do not unlock order creation until\n`VERIFIED`.\n\n**Signature verification**\n\nEvery delivery includes two headers for HMAC-SHA256 verification:\n- `X-Unigox-Signature: sha256=<hex>` — HMAC of `\"<timestamp>.<raw_body>\"`\n- `X-Unigox-Timestamp: <unix_seconds>`\n\nVerification pseudocode:\n```\nexpected = HMAC-SHA256(webhook_secret, \"<timestamp>.<raw_body>\")\nvalid    = constant_time_compare(expected, signature_from_header)\n```\n\n**Delivery guarantees**\n\n- At-least-once delivery. Your endpoint may receive the same event more than once.\n- De-duplicate by `event_id`.\n- Respond with any 2xx status within 10 seconds to acknowledge receipt.\n\n**Retry policy**\n\nFailed deliveries (non-2xx or timeout) are retried with exponential backoff:\n1 min → 5 min → 15 min → 1 h → 6 h → 12 h → 24 h (max 10 attempts over ~3.5 days).\n\n**Partner-facing statuses** (`order.status.changed`)\n\n| Status | Description |\n|---|---|\n| `created` | Order created |\n| `awaiting_liquidity_provider` | Waiting for vendor match |\n| `awaiting_crypto_transfer_authorization` | Partner must authorize crypto transfer |\n| `crypto_received` | Crypto received in escrow |\n| `fiat_payment_started` | Buyer submitted fiat payment proof |\n| `fiat_payment_review_started` | Payment proof under admin review |\n| `awaiting_fiat_received_confirmation` | Proof approved, awaiting seller confirmation |\n| `completed` | Order completed, crypto released |\n| `cancelled` | Order cancelled or payment declined |\n| `failed` | Escrow error, or fiat-anchored safety bound exceeded (see below) |\n| `dispute_started` | Dispute opened |\n\n**Fiat-anchored safety bound failure**\n\nFor fiat-anchored off-ramp orders (`anchor_type: \"fiat\"`), if the crypto cost we quote exceeds\n1.20× the expected amount at the time of settlement, the order moves to `failed` status asynchronously\n(not during `/initiate`). The deposit is automatically refunded from escrow back to the partner wallet.\nCreate a new quote to retry the trade flow.\n\n**Settlement notifications (recipient received the fiat)**\n\nSome liquidity providers automatically confirm when the fiat payout reaches the\nrecipient's bank account or mobile wallet. When the provider matched to an order\nsupports this, the order moves to `completed` on its own and you receive an\n`order.status.changed` webhook with `status=completed` — that webhook is your signal\nthat the recipient has actually received the money.\n\nWhether the matched provider sends this is reported per order by the\n`has_fiat_settlement_notification` field on the **quote** and **order** responses:\n\n- `true` — the matched provider sends an automatic settlement confirmation. You will\n  receive the `completed` webhook once fiat is delivered; no manual action is needed.\n- `false` — no automatic confirmation for this order. Confirm delivery yourself by\n  calling `confirm-fiat-received` (or rely on your own banking/payment systems).\n\nTo match **only** providers that send settlement notifications, pass\n`has_fiat_settlement_notification: true` in the quote request — providers that can't\noffer it are then excluded from matching.\n\nAvailability is provider- and corridor-dependent: not every currency has a provider\nthat sends settlement notifications, so always read the flag on each quote rather than\nassuming it. Manual `confirm-fiat-received` and the automatic confirmation are mutually\nidempotent — whichever happens first wins and the other is a safe no-op.\n"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"RegisterWebhookRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"HTTPS URL that will receive webhook events"}}},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"WebhookConfigResponse":{"type":"object","properties":{"url":{"type":"string","nullable":true,"description":"Currently configured webhook URL (null if not set)"},"enabled":{"type":"boolean","description":"Whether webhook delivery is active"}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}},"paths":{"/api/v1/partner/webhooks":{"post":{"tags":["Webhooks"],"summary":"Register webhook URL","description":"Register or update the webhook URL for your partner account. The URL must use HTTPS.\nOnce registered, the webhook is automatically enabled and will start receiving order status events.\n","operationId":"registerWebhook","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterWebhookRequest"}}}},"responses":{"200":{"description":"Webhook registered successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookConfigResponse"}}}]}}}},"400":{"description":"Bad request — missing or invalid URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key"}}}}}}
````

## Delete webhook

> Remove the webhook URL and disable webhook delivery for your partner account.\
> No further events will be sent until a new URL is registered.<br>

````json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Webhooks","description":"Real-time notifications for order and KYC status changes.\nThe system sends a signed HTTP POST to your configured webhook URL on every relevant event.\n\n**Event model**\n\nTwo event types are delivered:\n\n| Event type | Fired when |\n|---|---|\n| `order.status.changed` | An order transitions to a new status |\n| `user.kyc.updated` | A user's KYC verification is approved or declined |\n\nAll events share the same envelope (`event_id`, `event_type`, `created_at`, `data`)\nand the same signature scheme. The structure of the `data` object differs per event type.\n\n**Payload shape — `order.status.changed`**\n\n```json\n{\n  \"event_id\": \"evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n  \"event_type\": \"order.status.changed\",\n  \"created_at\": \"2026-02-01T12:00:05Z\",\n  \"data\": {\n    \"order_id\": \"b2c3d4e5-f6a7-8901-bcde-f12345678901\",\n    \"status\": \"crypto_received\",\n    \"user_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"crypto_amount\": \"101.5\",\n    \"crypto_currency\": \"USDT\",\n    \"partner_fee\": \"1\",\n    \"partner_fee_pct\": 1,\n    \"fiat_amount\": \"152880.00\",\n    \"fiat_currency\": \"NGN\",\n    \"provider\": \"p2p\",\n    \"payment_details_id\": \"987\"\n  }\n}\n```\n\n**Payload shape — `user.kyc.updated`**\n\nFired when a user's KYC status changes — to `verified`, `rejected`, or to a manual compliance\nreview hold (`UNDER_REVIEW`). Use this event to unlock order creation for the user on your side\n(only on `VERIFIED`; `UNDER_REVIEW` is not cleared).\n\n```json\n{\n  \"event_id\": \"evt_2d8418dd-e3a8-4463-a11e-6b54f354ca90\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T11:37:42Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"VERIFIED\"\n  }\n}\n```\n\nWhen KYC is declined, `rejection_reasons` is included:\n\n```json\n{\n  \"event_id\": \"evt_fc75484d-b374-4d05-b54e-820f3dd80e6d\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:00:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"REJECTED\",\n    \"rejection_reasons\": [\"Document expired\", \"Photo quality low\"]\n  }\n}\n```\n\nWhen a customer is placed on a manual compliance review hold, `kyc_status` is `UNDER_REVIEW`:\n\n```json\n{\n  \"event_id\": \"evt_9a1c7f20-4d3b-4e88-9f2a-1b6d0c5e7a3f\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:05:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"UNDER_REVIEW\"\n  }\n}\n```\n\n`kyc_status` values: `VERIFIED` | `REJECTED` | `UNDER_REVIEW`\n\n`UNDER_REVIEW` is a manual compliance review: the customer is NOT cleared and can take up to\n24 hours (unlike an automated check of a couple of minutes). Do not unlock order creation until\n`VERIFIED`.\n\n**Signature verification**\n\nEvery delivery includes two headers for HMAC-SHA256 verification:\n- `X-Unigox-Signature: sha256=<hex>` — HMAC of `\"<timestamp>.<raw_body>\"`\n- `X-Unigox-Timestamp: <unix_seconds>`\n\nVerification pseudocode:\n```\nexpected = HMAC-SHA256(webhook_secret, \"<timestamp>.<raw_body>\")\nvalid    = constant_time_compare(expected, signature_from_header)\n```\n\n**Delivery guarantees**\n\n- At-least-once delivery. Your endpoint may receive the same event more than once.\n- De-duplicate by `event_id`.\n- Respond with any 2xx status within 10 seconds to acknowledge receipt.\n\n**Retry policy**\n\nFailed deliveries (non-2xx or timeout) are retried with exponential backoff:\n1 min → 5 min → 15 min → 1 h → 6 h → 12 h → 24 h (max 10 attempts over ~3.5 days).\n\n**Partner-facing statuses** (`order.status.changed`)\n\n| Status | Description |\n|---|---|\n| `created` | Order created |\n| `awaiting_liquidity_provider` | Waiting for vendor match |\n| `awaiting_crypto_transfer_authorization` | Partner must authorize crypto transfer |\n| `crypto_received` | Crypto received in escrow |\n| `fiat_payment_started` | Buyer submitted fiat payment proof |\n| `fiat_payment_review_started` | Payment proof under admin review |\n| `awaiting_fiat_received_confirmation` | Proof approved, awaiting seller confirmation |\n| `completed` | Order completed, crypto released |\n| `cancelled` | Order cancelled or payment declined |\n| `failed` | Escrow error, or fiat-anchored safety bound exceeded (see below) |\n| `dispute_started` | Dispute opened |\n\n**Fiat-anchored safety bound failure**\n\nFor fiat-anchored off-ramp orders (`anchor_type: \"fiat\"`), if the crypto cost we quote exceeds\n1.20× the expected amount at the time of settlement, the order moves to `failed` status asynchronously\n(not during `/initiate`). The deposit is automatically refunded from escrow back to the partner wallet.\nCreate a new quote to retry the trade flow.\n\n**Settlement notifications (recipient received the fiat)**\n\nSome liquidity providers automatically confirm when the fiat payout reaches the\nrecipient's bank account or mobile wallet. When the provider matched to an order\nsupports this, the order moves to `completed` on its own and you receive an\n`order.status.changed` webhook with `status=completed` — that webhook is your signal\nthat the recipient has actually received the money.\n\nWhether the matched provider sends this is reported per order by the\n`has_fiat_settlement_notification` field on the **quote** and **order** responses:\n\n- `true` — the matched provider sends an automatic settlement confirmation. You will\n  receive the `completed` webhook once fiat is delivered; no manual action is needed.\n- `false` — no automatic confirmation for this order. Confirm delivery yourself by\n  calling `confirm-fiat-received` (or rely on your own banking/payment systems).\n\nTo match **only** providers that send settlement notifications, pass\n`has_fiat_settlement_notification: true` in the quote request — providers that can't\noffer it are then excluded from matching.\n\nAvailability is provider- and corridor-dependent: not every currency has a provider\nthat sends settlement notifications, so always read the flag on each quote rather than\nassuming it. Manual `confirm-fiat-received` and the automatic confirmation are mutually\nidempotent — whichever happens first wins and the other is a safe no-op.\n"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}}}},"paths":{"/api/v1/partner/webhooks/{id}":{"delete":{"tags":["Webhooks"],"summary":"Delete webhook","description":"Remove the webhook URL and disable webhook delivery for your partner account.\nNo further events will be sent until a new URL is registered.\n","operationId":"deleteWebhook","parameters":[{"name":"id","in":"path","required":true,"description":"Webhook ID (any value accepted — the system uses the authenticated partner context)","schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook deleted successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"message":{"type":"string"}}}}}]}}}},"401":{"description":"Unauthorized — invalid or missing API key"}}}}}}
````

## Test webhook

> Send a signed test event to your configured webhook URL. Uses the same HMAC-SHA256\
> signature scheme as production events. Use this to verify your endpoint is reachable\
> and correctly validating signatures.<br>

````json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"tags":[{"name":"Webhooks","description":"Real-time notifications for order and KYC status changes.\nThe system sends a signed HTTP POST to your configured webhook URL on every relevant event.\n\n**Event model**\n\nTwo event types are delivered:\n\n| Event type | Fired when |\n|---|---|\n| `order.status.changed` | An order transitions to a new status |\n| `user.kyc.updated` | A user's KYC verification is approved or declined |\n\nAll events share the same envelope (`event_id`, `event_type`, `created_at`, `data`)\nand the same signature scheme. The structure of the `data` object differs per event type.\n\n**Payload shape — `order.status.changed`**\n\n```json\n{\n  \"event_id\": \"evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n  \"event_type\": \"order.status.changed\",\n  \"created_at\": \"2026-02-01T12:00:05Z\",\n  \"data\": {\n    \"order_id\": \"b2c3d4e5-f6a7-8901-bcde-f12345678901\",\n    \"status\": \"crypto_received\",\n    \"user_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"crypto_amount\": \"101.5\",\n    \"crypto_currency\": \"USDT\",\n    \"partner_fee\": \"1\",\n    \"partner_fee_pct\": 1,\n    \"fiat_amount\": \"152880.00\",\n    \"fiat_currency\": \"NGN\",\n    \"provider\": \"p2p\",\n    \"payment_details_id\": \"987\"\n  }\n}\n```\n\n**Payload shape — `user.kyc.updated`**\n\nFired when a user's KYC status changes — to `verified`, `rejected`, or to a manual compliance\nreview hold (`UNDER_REVIEW`). Use this event to unlock order creation for the user on your side\n(only on `VERIFIED`; `UNDER_REVIEW` is not cleared).\n\n```json\n{\n  \"event_id\": \"evt_2d8418dd-e3a8-4463-a11e-6b54f354ca90\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T11:37:42Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"VERIFIED\"\n  }\n}\n```\n\nWhen KYC is declined, `rejection_reasons` is included:\n\n```json\n{\n  \"event_id\": \"evt_fc75484d-b374-4d05-b54e-820f3dd80e6d\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:00:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"REJECTED\",\n    \"rejection_reasons\": [\"Document expired\", \"Photo quality low\"]\n  }\n}\n```\n\nWhen a customer is placed on a manual compliance review hold, `kyc_status` is `UNDER_REVIEW`:\n\n```json\n{\n  \"event_id\": \"evt_9a1c7f20-4d3b-4e88-9f2a-1b6d0c5e7a3f\",\n  \"event_type\": \"user.kyc.updated\",\n  \"created_at\": \"2026-03-10T12:05:00Z\",\n  \"data\": {\n    \"user_uuid\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"kyc_status\": \"UNDER_REVIEW\"\n  }\n}\n```\n\n`kyc_status` values: `VERIFIED` | `REJECTED` | `UNDER_REVIEW`\n\n`UNDER_REVIEW` is a manual compliance review: the customer is NOT cleared and can take up to\n24 hours (unlike an automated check of a couple of minutes). Do not unlock order creation until\n`VERIFIED`.\n\n**Signature verification**\n\nEvery delivery includes two headers for HMAC-SHA256 verification:\n- `X-Unigox-Signature: sha256=<hex>` — HMAC of `\"<timestamp>.<raw_body>\"`\n- `X-Unigox-Timestamp: <unix_seconds>`\n\nVerification pseudocode:\n```\nexpected = HMAC-SHA256(webhook_secret, \"<timestamp>.<raw_body>\")\nvalid    = constant_time_compare(expected, signature_from_header)\n```\n\n**Delivery guarantees**\n\n- At-least-once delivery. Your endpoint may receive the same event more than once.\n- De-duplicate by `event_id`.\n- Respond with any 2xx status within 10 seconds to acknowledge receipt.\n\n**Retry policy**\n\nFailed deliveries (non-2xx or timeout) are retried with exponential backoff:\n1 min → 5 min → 15 min → 1 h → 6 h → 12 h → 24 h (max 10 attempts over ~3.5 days).\n\n**Partner-facing statuses** (`order.status.changed`)\n\n| Status | Description |\n|---|---|\n| `created` | Order created |\n| `awaiting_liquidity_provider` | Waiting for vendor match |\n| `awaiting_crypto_transfer_authorization` | Partner must authorize crypto transfer |\n| `crypto_received` | Crypto received in escrow |\n| `fiat_payment_started` | Buyer submitted fiat payment proof |\n| `fiat_payment_review_started` | Payment proof under admin review |\n| `awaiting_fiat_received_confirmation` | Proof approved, awaiting seller confirmation |\n| `completed` | Order completed, crypto released |\n| `cancelled` | Order cancelled or payment declined |\n| `failed` | Escrow error, or fiat-anchored safety bound exceeded (see below) |\n| `dispute_started` | Dispute opened |\n\n**Fiat-anchored safety bound failure**\n\nFor fiat-anchored off-ramp orders (`anchor_type: \"fiat\"`), if the crypto cost we quote exceeds\n1.20× the expected amount at the time of settlement, the order moves to `failed` status asynchronously\n(not during `/initiate`). The deposit is automatically refunded from escrow back to the partner wallet.\nCreate a new quote to retry the trade flow.\n\n**Settlement notifications (recipient received the fiat)**\n\nSome liquidity providers automatically confirm when the fiat payout reaches the\nrecipient's bank account or mobile wallet. When the provider matched to an order\nsupports this, the order moves to `completed` on its own and you receive an\n`order.status.changed` webhook with `status=completed` — that webhook is your signal\nthat the recipient has actually received the money.\n\nWhether the matched provider sends this is reported per order by the\n`has_fiat_settlement_notification` field on the **quote** and **order** responses:\n\n- `true` — the matched provider sends an automatic settlement confirmation. You will\n  receive the `completed` webhook once fiat is delivered; no manual action is needed.\n- `false` — no automatic confirmation for this order. Confirm delivery yourself by\n  calling `confirm-fiat-received` (or rely on your own banking/payment systems).\n\nTo match **only** providers that send settlement notifications, pass\n`has_fiat_settlement_notification: true` in the quote request — providers that can't\noffer it are then excluded from matching.\n\nAvailability is provider- and corridor-dependent: not every currency has a provider\nthat sends settlement notifications, so always read the flag on each quote rather than\nassuming it. Manual `confirm-fiat-received` and the automatic confirmation are mutually\nidempotent — whichever happens first wins and the other is a safe no-op.\n"}],"servers":[{"url":"https://api-staging.unigox.com","description":"Sandbox server"},{"url":"https://api.unigox.com","description":"Production server"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Partner API key for authentication. Required for all partner account endpoints."}},"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"WebhookTestPingResponse":{"type":"object","properties":{"delivered":{"type":"boolean","description":"Whether the test event was delivered successfully"},"http_status":{"type":"integer","description":"HTTP status code returned by the webhook endpoint"}}},"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}},"paths":{"/api/v1/partner/webhooks/test":{"post":{"tags":["Webhooks"],"summary":"Test webhook","description":"Send a signed test event to your configured webhook URL. Uses the same HMAC-SHA256\nsignature scheme as production events. Use this to verify your endpoint is reachable\nand correctly validating signatures.\n","operationId":"testPingWebhook","responses":{"200":{"description":"Test event delivered successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookTestPingResponse"}}}]}}}},"400":{"description":"Webhook URL not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized — invalid or missing API key"},"502":{"description":"Webhook delivery failed — target endpoint returned non-2xx or timed out","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}
````


# Models

## The KycDocumentType object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"KycDocumentType":{"type":"string","description":"Complete set of document types accepted by the partner KYC upload endpoint. `document_type` only selects the upload slot (front/back/selfie); it is not a document-category and does not set the country — our verification system detects the document type from the image, and the country comes from the customer's KYC profile. Values named after a country (`ghana_card`, `ghana_ssnit_card`, `kenya_alien_card`, `green_book`, `colombia_ppt`) are country-specific hints; all others (`passport`, `national_id`, `driver_license`, `voter_id`, `resident_card`, `id_card`, `tax_id`, `identity_front`/`identity_back`, `selfie`) are general — any country. Nigeria's NIN is submitted as `national_id`.","enum":["identity_front","identity_back","selfie","passport","passport_front","national_id","national_id_front","national_id_back","driver_license","driver_license_front","driver_license_back","voter_id","voter_id_front","voter_id_back","resident_card","resident_card_front","resident_card_back","id_card","id_card_front","id_card_back","ghana_card","ghana_card_front","ghana_card_back","ghana_ssnit_card","ghana_ssnit_card_front","ghana_ssnit_card_back","kenya_alien_card","kenya_alien_card_front","kenya_alien_card_back","green_book","green_book_front","green_book_back","tax_id","tax_id_front","tax_id_back","colombia_ppt","colombia_ppt_front","colombia_ppt_back"]}}}}
```

## The APIResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}}}}}
```

## The ErrorResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"ErrorResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["error_key"],"properties":{"error_key":{"type":"string","description":"Standardized error key for client error handling"}}}}}}}}
```

## The CryptoCurrencyCapability object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"CryptoCurrencyCapability":{"type":"object","properties":{"code":{"type":"string","description":"Cryptocurrency code (e.g., BTC, ETH)"},"name":{"type":"string","description":"Cryptocurrency name"},"contract":{"type":"string","description":"Token contract address (for ERC-20 tokens)"},"decimals":{"type":"integer","format":"int64","description":"Token decimals (number of decimal places)"},"blockchains":{"type":"array","items":{"type":"string"},"description":"List of supported blockchain names/slugs"}}}}}}
```

## The CryptoCurrenciesResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"CryptoCurrenciesResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CryptoCurrencyCapability"}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"CryptoCurrencyCapability":{"type":"object","properties":{"code":{"type":"string","description":"Cryptocurrency code (e.g., BTC, ETH)"},"name":{"type":"string","description":"Cryptocurrency name"},"contract":{"type":"string","description":"Token contract address (for ERC-20 tokens)"},"decimals":{"type":"integer","format":"int64","description":"Token decimals (number of decimal places)"},"blockchains":{"type":"array","items":{"type":"string"},"description":"List of supported blockchain names/slugs"}}}}}}
```

## The BlockchainCapability object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"BlockchainCapability":{"type":"object","properties":{"name":{"type":"string","description":"Blockchain name"},"slug":{"type":"string","description":"Blockchain slug identifier"},"ticker":{"type":"string","description":"Blockchain ticker symbol"},"crypto_currencies":{"type":"array","items":{"$ref":"#/components/schemas/CryptoCurrencyCapability"},"description":"List of cryptocurrencies supported on this blockchain"}}},"CryptoCurrencyCapability":{"type":"object","properties":{"code":{"type":"string","description":"Cryptocurrency code (e.g., BTC, ETH)"},"name":{"type":"string","description":"Cryptocurrency name"},"contract":{"type":"string","description":"Token contract address (for ERC-20 tokens)"},"decimals":{"type":"integer","format":"int64","description":"Token decimals (number of decimal places)"},"blockchains":{"type":"array","items":{"type":"string"},"description":"List of supported blockchain names/slugs"}}}}}}
```

## The BlockchainsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"BlockchainsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BlockchainCapability"}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"BlockchainCapability":{"type":"object","properties":{"name":{"type":"string","description":"Blockchain name"},"slug":{"type":"string","description":"Blockchain slug identifier"},"ticker":{"type":"string","description":"Blockchain ticker symbol"},"crypto_currencies":{"type":"array","items":{"$ref":"#/components/schemas/CryptoCurrencyCapability"},"description":"List of cryptocurrencies supported on this blockchain"}}},"CryptoCurrencyCapability":{"type":"object","properties":{"code":{"type":"string","description":"Cryptocurrency code (e.g., BTC, ETH)"},"name":{"type":"string","description":"Cryptocurrency name"},"contract":{"type":"string","description":"Token contract address (for ERC-20 tokens)"},"decimals":{"type":"integer","format":"int64","description":"Token decimals (number of decimal places)"},"blockchains":{"type":"array","items":{"type":"string"},"description":"List of supported blockchain names/slugs"}}}}}}
```

## The FiatCurrencyCapability object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"FiatCurrencyCapability":{"type":"object","properties":{"code":{"type":"string","description":"Fiat currency code (ISO 4217)"},"name":{"type":"string","description":"Fiat currency name"},"institution_id":{"type":"array","items":{"type":"string"},"description":"List of payment method slugs that support this currency (retrieved from active payment method options)"},"countries":{"type":"array","items":{"type":"string"},"description":"List of ISO 2-letter country codes where this currency is supported (retrieved from active payment method options)"},"onramp":{"type":"boolean","description":"Whether onramp (fiat to crypto) is supported. Currently set to true if there are any active payment method options for this currency.\nThis will be refined in future updates based on trade types and explicit direction configuration.\n"},"offramp":{"type":"boolean","description":"Whether offramp (crypto to fiat) is supported. Currently set to true if there are any active payment method options for this currency.\nThis will be refined in future updates based on trade types and explicit direction configuration.\n"}}}}}}
```

## The FiatCurrenciesResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"FiatCurrenciesResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/FiatCurrencyCapability"}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"FiatCurrencyCapability":{"type":"object","properties":{"code":{"type":"string","description":"Fiat currency code (ISO 4217)"},"name":{"type":"string","description":"Fiat currency name"},"institution_id":{"type":"array","items":{"type":"string"},"description":"List of payment method slugs that support this currency (retrieved from active payment method options)"},"countries":{"type":"array","items":{"type":"string"},"description":"List of ISO 2-letter country codes where this currency is supported (retrieved from active payment method options)"},"onramp":{"type":"boolean","description":"Whether onramp (fiat to crypto) is supported. Currently set to true if there are any active payment method options for this currency.\nThis will be refined in future updates based on trade types and explicit direction configuration.\n"},"offramp":{"type":"boolean","description":"Whether offramp (crypto to fiat) is supported. Currently set to true if there are any active payment method options for this currency.\nThis will be refined in future updates based on trade types and explicit direction configuration.\n"}}}}}}
```

## The CorridorRateQuote object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"CorridorRateQuote":{"type":"object","description":"One partner-type variant of a corridor rate (P2P or Licensed).\n","properties":{"rate":{"type":"number","format":"double","nullable":true,"description":"All-in fiat-per-USDT rate (platform fee already applied)."},"min_notional_fiat":{"type":"number","format":"double","nullable":true,"description":"Smallest amount accepted by any active offer in this variant, in the corridor's local currency. Set only when it exceeds the reference notional in dollar terms — i.e. the customer would need to size up to use this variant."}}}}}}
```

## The CorridorDirectionInfo object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"CorridorDirectionInfo":{"type":"object","description":"Per-direction info for one fiat corridor. The whole object is\nomitted when no active offer exists in that direction. Either\n`p2p` or `licensed` (or both) will be present.\n","properties":{"supported":{"type":"boolean","description":"Always `true` when the object is present (the object is omitted otherwise). Reserved for future states where a corridor may be configured but temporarily without liquidity."},"p2p":{"$ref":"#/components/schemas/CorridorRateQuote"},"licensed":{"$ref":"#/components/schemas/CorridorRateQuote"}}},"CorridorRateQuote":{"type":"object","description":"One partner-type variant of a corridor rate (P2P or Licensed).\n","properties":{"rate":{"type":"number","format":"double","nullable":true,"description":"All-in fiat-per-USDT rate (platform fee already applied)."},"min_notional_fiat":{"type":"number","format":"double","nullable":true,"description":"Smallest amount accepted by any active offer in this variant, in the corridor's local currency. Set only when it exceeds the reference notional in dollar terms — i.e. the customer would need to size up to use this variant."}}}}}}
```

## The Corridor object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"Corridor":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"mid_market_rate":{"type":"number","format":"double","nullable":true,"description":"Open Exchange Rates reference rate (fiat per USDT) for this currency. Same value applies to both directions."},"mid_market_updated_at":{"type":"string","format":"date-time","nullable":true},"unigox_fee_pct":{"type":"number","format":"double","nullable":true,"description":"Cheapest active Unigox platform fee for this corridor, in percent. Already baked into every `rate` below."},"onramp":{"$ref":"#/components/schemas/CorridorDirectionInfo"},"offramp":{"$ref":"#/components/schemas/CorridorDirectionInfo"}}},"CorridorDirectionInfo":{"type":"object","description":"Per-direction info for one fiat corridor. The whole object is\nomitted when no active offer exists in that direction. Either\n`p2p` or `licensed` (or both) will be present.\n","properties":{"supported":{"type":"boolean","description":"Always `true` when the object is present (the object is omitted otherwise). Reserved for future states where a corridor may be configured but temporarily without liquidity."},"p2p":{"$ref":"#/components/schemas/CorridorRateQuote"},"licensed":{"$ref":"#/components/schemas/CorridorRateQuote"}}},"CorridorRateQuote":{"type":"object","description":"One partner-type variant of a corridor rate (P2P or Licensed).\n","properties":{"rate":{"type":"number","format":"double","nullable":true,"description":"All-in fiat-per-USDT rate (platform fee already applied)."},"min_notional_fiat":{"type":"number","format":"double","nullable":true,"description":"Smallest amount accepted by any active offer in this variant, in the corridor's local currency. Set only when it exceeds the reference notional in dollar terms — i.e. the customer would need to size up to use this variant."}}}}}}
```

## The SupportedCorridorsPayload object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"SupportedCorridorsPayload":{"type":"object","properties":{"notional_usdt":{"type":"number","format":"double"},"generated_at":{"type":"string","format":"date-time"},"currencies":{"type":"array","items":{"$ref":"#/components/schemas/Corridor"}}}},"Corridor":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"mid_market_rate":{"type":"number","format":"double","nullable":true,"description":"Open Exchange Rates reference rate (fiat per USDT) for this currency. Same value applies to both directions."},"mid_market_updated_at":{"type":"string","format":"date-time","nullable":true},"unigox_fee_pct":{"type":"number","format":"double","nullable":true,"description":"Cheapest active Unigox platform fee for this corridor, in percent. Already baked into every `rate` below."},"onramp":{"$ref":"#/components/schemas/CorridorDirectionInfo"},"offramp":{"$ref":"#/components/schemas/CorridorDirectionInfo"}}},"CorridorDirectionInfo":{"type":"object","description":"Per-direction info for one fiat corridor. The whole object is\nomitted when no active offer exists in that direction. Either\n`p2p` or `licensed` (or both) will be present.\n","properties":{"supported":{"type":"boolean","description":"Always `true` when the object is present (the object is omitted otherwise). Reserved for future states where a corridor may be configured but temporarily without liquidity."},"p2p":{"$ref":"#/components/schemas/CorridorRateQuote"},"licensed":{"$ref":"#/components/schemas/CorridorRateQuote"}}},"CorridorRateQuote":{"type":"object","description":"One partner-type variant of a corridor rate (P2P or Licensed).\n","properties":{"rate":{"type":"number","format":"double","nullable":true,"description":"All-in fiat-per-USDT rate (platform fee already applied)."},"min_notional_fiat":{"type":"number","format":"double","nullable":true,"description":"Smallest amount accepted by any active offer in this variant, in the corridor's local currency. Set only when it exceeds the reference notional in dollar terms — i.e. the customer would need to size up to use this variant."}}}}}}
```

## The SupportedCorridorsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"SupportedCorridorsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SupportedCorridorsPayload"}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"SupportedCorridorsPayload":{"type":"object","properties":{"notional_usdt":{"type":"number","format":"double"},"generated_at":{"type":"string","format":"date-time"},"currencies":{"type":"array","items":{"$ref":"#/components/schemas/Corridor"}}}},"Corridor":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"mid_market_rate":{"type":"number","format":"double","nullable":true,"description":"Open Exchange Rates reference rate (fiat per USDT) for this currency. Same value applies to both directions."},"mid_market_updated_at":{"type":"string","format":"date-time","nullable":true},"unigox_fee_pct":{"type":"number","format":"double","nullable":true,"description":"Cheapest active Unigox platform fee for this corridor, in percent. Already baked into every `rate` below."},"onramp":{"$ref":"#/components/schemas/CorridorDirectionInfo"},"offramp":{"$ref":"#/components/schemas/CorridorDirectionInfo"}}},"CorridorDirectionInfo":{"type":"object","description":"Per-direction info for one fiat corridor. The whole object is\nomitted when no active offer exists in that direction. Either\n`p2p` or `licensed` (or both) will be present.\n","properties":{"supported":{"type":"boolean","description":"Always `true` when the object is present (the object is omitted otherwise). Reserved for future states where a corridor may be configured but temporarily without liquidity."},"p2p":{"$ref":"#/components/schemas/CorridorRateQuote"},"licensed":{"$ref":"#/components/schemas/CorridorRateQuote"}}},"CorridorRateQuote":{"type":"object","description":"One partner-type variant of a corridor rate (P2P or Licensed).\n","properties":{"rate":{"type":"number","format":"double","nullable":true,"description":"All-in fiat-per-USDT rate (platform fee already applied)."},"min_notional_fiat":{"type":"number","format":"double","nullable":true,"description":"Smallest amount accepted by any active offer in this variant, in the corridor's local currency. Set only when it exceeds the reference notional in dollar terms — i.e. the customer would need to size up to use this variant."}}}}}}
```

## The PaymentField object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PaymentField":{"type":"object","properties":{"key":{"type":"string","description":"Field identifier/key"},"label":{"type":"string","description":"Human-readable field label"},"type":{"type":"string","description":"Field input type (text, number, select, etc.)"},"required":{"type":"boolean","description":"Whether this field is required"},"pattern":{"type":"string","description":"Regex pattern for validation (optional)"},"placeholder":{"type":"string","description":"Placeholder text for input (optional)"},"lookup":{"type":"boolean","description":"Whether field can be used for lookup (optional)"},"options":{"type":"array","description":"Options for select fields (optional)","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"}}}}}}}}}
```

## The Rails object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"Rails":{"type":"object","properties":{"slug":{"type":"string","description":"Payment rail slug identifier"},"name":{"type":"string","description":"Payment rail name"},"institution_required":{"type":"boolean","description":"Whether institution (bank) selection is required by the rail itself. In the current partner API, omission of `institution_id` is explicitly supported for `iban-sepa`, `nip-nigeria`, and `imps-neft-india` (which returns `false` and is routed by `ifsc_code` instead)."},"institution_lookup_key":{"type":"string","nullable":true,"description":"Lookup key for institution search, e.g., ifsc or sort_code"},"currencies":{"type":"array","items":{"type":"string"},"description":"List of supported fiat currency codes"},"settlement":{"type":"object","nullable":true,"description":"Settlement information","properties":{"speed":{"type":"string","enum":["INSTANT","SAME_DAY","NEXT_DAY","STANDARD"],"description":"Settlement speed"},"typical_minutes":{"type":"integer","description":"Typical settlement time in minutes"},"note":{"type":"string","nullable":true,"description":"Optional note about settlement"}}},"limits":{"type":"object","nullable":true,"description":"Transaction limits","properties":{"min_amount":{"type":"number","nullable":true,"description":"Minimum transaction amount"},"max_amount":{"type":"number","nullable":true,"description":"Maximum transaction amount (null = no limit)"},"max_per_transaction":{"type":"number","nullable":true,"description":"Maximum per transaction (null = no limit)"}}},"fields":{"type":"array","items":{"$ref":"#/components/schemas/PaymentField"},"description":"Required fields for payment details input. For a multi-format rail (see `formats`) this is a single best-effort field set kept for backward compatibility; the authoritative, institution-specific fields are in `formats`.\n"},"formats":{"type":"array","description":"Present only when a rail accepts more than one field-set (e.g. GHIPSS and EthSwitch accept a mobile-money format keyed on `phone_number` and a bank format keyed on `account_number`). The required field set depends on the chosen institution's type, so pick an institution from `/api/v1/supported/institutions`, match its `institution_type` to a format via `institution_types`, and submit that format's `fields`.\n","items":{"$ref":"#/components/schemas/RailFormat"}}}},"PaymentField":{"type":"object","properties":{"key":{"type":"string","description":"Field identifier/key"},"label":{"type":"string","description":"Human-readable field label"},"type":{"type":"string","description":"Field input type (text, number, select, etc.)"},"required":{"type":"boolean","description":"Whether this field is required"},"pattern":{"type":"string","description":"Regex pattern for validation (optional)"},"placeholder":{"type":"string","description":"Placeholder text for input (optional)"},"lookup":{"type":"boolean","description":"Whether field can be used for lookup (optional)"},"options":{"type":"array","description":"Options for select fields (optional)","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"}}}}}},"RailFormat":{"type":"object","properties":{"id":{"type":"string","description":"Format identifier"},"name":{"type":"string","description":"Human-readable format name"},"institution_types":{"type":"array","items":{"type":"string"},"description":"Institution types this format applies to. Match the chosen institution's type to select the format."},"has_liquidity":{"type":"boolean","description":"Whether a vendor/provider can currently settle this format in the corridor. Omitted when liquidity could not be determined (treat as unknown, not \"no\"). This reflects configured liquidity; live availability is confirmed at quote time.\n"},"beneficiary_type":{"type":"string","enum":["individual","business"],"description":"Present only for rails that distinguish an individual vs a business beneficiary. A `business` format shares its individual sibling's `institution_types` but collects a company's details. To use it, submit this format's `fields` AND set `\"beneficiary_type\": \"business\"` inside the create-payment-details `details` object. Omitting `\"beneficiary_type\"` in the request details defaults to the individual variant.\n"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/PaymentField"},"description":"Required fields for payment details input when paying out to an institution of this format's type."}}}}}}
```

## The RailFormat object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"RailFormat":{"type":"object","properties":{"id":{"type":"string","description":"Format identifier"},"name":{"type":"string","description":"Human-readable format name"},"institution_types":{"type":"array","items":{"type":"string"},"description":"Institution types this format applies to. Match the chosen institution's type to select the format."},"has_liquidity":{"type":"boolean","description":"Whether a vendor/provider can currently settle this format in the corridor. Omitted when liquidity could not be determined (treat as unknown, not \"no\"). This reflects configured liquidity; live availability is confirmed at quote time.\n"},"beneficiary_type":{"type":"string","enum":["individual","business"],"description":"Present only for rails that distinguish an individual vs a business beneficiary. A `business` format shares its individual sibling's `institution_types` but collects a company's details. To use it, submit this format's `fields` AND set `\"beneficiary_type\": \"business\"` inside the create-payment-details `details` object. Omitting `\"beneficiary_type\"` in the request details defaults to the individual variant.\n"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/PaymentField"},"description":"Required fields for payment details input when paying out to an institution of this format's type."}}},"PaymentField":{"type":"object","properties":{"key":{"type":"string","description":"Field identifier/key"},"label":{"type":"string","description":"Human-readable field label"},"type":{"type":"string","description":"Field input type (text, number, select, etc.)"},"required":{"type":"boolean","description":"Whether this field is required"},"pattern":{"type":"string","description":"Regex pattern for validation (optional)"},"placeholder":{"type":"string","description":"Placeholder text for input (optional)"},"lookup":{"type":"boolean","description":"Whether field can be used for lookup (optional)"},"options":{"type":"array","description":"Options for select fields (optional)","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"}}}}}}}}}
```

## The PaymentRailsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PaymentRailsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","required":["country","direction","rails"],"properties":{"country":{"type":"string","description":"Country code used in the query"},"currency":{"type":"string","description":"Currency code (from query parameter or first rail)"},"direction":{"type":"string","enum":["onramp","offramp"],"description":"Transaction direction"},"rails":{"type":"array","items":{"$ref":"#/components/schemas/Rails"},"description":"List of available payment rails"},"links":{"type":"object","additionalProperties":{"type":"string"},"description":"Related API endpoints"}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"Rails":{"type":"object","properties":{"slug":{"type":"string","description":"Payment rail slug identifier"},"name":{"type":"string","description":"Payment rail name"},"institution_required":{"type":"boolean","description":"Whether institution (bank) selection is required by the rail itself. In the current partner API, omission of `institution_id` is explicitly supported for `iban-sepa`, `nip-nigeria`, and `imps-neft-india` (which returns `false` and is routed by `ifsc_code` instead)."},"institution_lookup_key":{"type":"string","nullable":true,"description":"Lookup key for institution search, e.g., ifsc or sort_code"},"currencies":{"type":"array","items":{"type":"string"},"description":"List of supported fiat currency codes"},"settlement":{"type":"object","nullable":true,"description":"Settlement information","properties":{"speed":{"type":"string","enum":["INSTANT","SAME_DAY","NEXT_DAY","STANDARD"],"description":"Settlement speed"},"typical_minutes":{"type":"integer","description":"Typical settlement time in minutes"},"note":{"type":"string","nullable":true,"description":"Optional note about settlement"}}},"limits":{"type":"object","nullable":true,"description":"Transaction limits","properties":{"min_amount":{"type":"number","nullable":true,"description":"Minimum transaction amount"},"max_amount":{"type":"number","nullable":true,"description":"Maximum transaction amount (null = no limit)"},"max_per_transaction":{"type":"number","nullable":true,"description":"Maximum per transaction (null = no limit)"}}},"fields":{"type":"array","items":{"$ref":"#/components/schemas/PaymentField"},"description":"Required fields for payment details input. For a multi-format rail (see `formats`) this is a single best-effort field set kept for backward compatibility; the authoritative, institution-specific fields are in `formats`.\n"},"formats":{"type":"array","description":"Present only when a rail accepts more than one field-set (e.g. GHIPSS and EthSwitch accept a mobile-money format keyed on `phone_number` and a bank format keyed on `account_number`). The required field set depends on the chosen institution's type, so pick an institution from `/api/v1/supported/institutions`, match its `institution_type` to a format via `institution_types`, and submit that format's `fields`.\n","items":{"$ref":"#/components/schemas/RailFormat"}}}},"PaymentField":{"type":"object","properties":{"key":{"type":"string","description":"Field identifier/key"},"label":{"type":"string","description":"Human-readable field label"},"type":{"type":"string","description":"Field input type (text, number, select, etc.)"},"required":{"type":"boolean","description":"Whether this field is required"},"pattern":{"type":"string","description":"Regex pattern for validation (optional)"},"placeholder":{"type":"string","description":"Placeholder text for input (optional)"},"lookup":{"type":"boolean","description":"Whether field can be used for lookup (optional)"},"options":{"type":"array","description":"Options for select fields (optional)","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"}}}}}},"RailFormat":{"type":"object","properties":{"id":{"type":"string","description":"Format identifier"},"name":{"type":"string","description":"Human-readable format name"},"institution_types":{"type":"array","items":{"type":"string"},"description":"Institution types this format applies to. Match the chosen institution's type to select the format."},"has_liquidity":{"type":"boolean","description":"Whether a vendor/provider can currently settle this format in the corridor. Omitted when liquidity could not be determined (treat as unknown, not \"no\"). This reflects configured liquidity; live availability is confirmed at quote time.\n"},"beneficiary_type":{"type":"string","enum":["individual","business"],"description":"Present only for rails that distinguish an individual vs a business beneficiary. A `business` format shares its individual sibling's `institution_types` but collects a company's details. To use it, submit this format's `fields` AND set `\"beneficiary_type\": \"business\"` inside the create-payment-details `details` object. Omitting `\"beneficiary_type\"` in the request details defaults to the individual variant.\n"},"fields":{"type":"array","items":{"$ref":"#/components/schemas/PaymentField"},"description":"Required fields for payment details input when paying out to an institution of this format's type."}}}}}}
```

## The InstitutionBranch object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"InstitutionBranch":{"type":"object","nullable":true,"properties":{"code":{"type":"string","description":"Branch code (e.g., IFSC, sort code)"},"name":{"type":"string","description":"Branch name"},"address":{"type":"string","description":"Branch address"}}}}}}
```

## The Institution object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"Institution":{"type":"object","properties":{"id":{"type":"string","description":"Institution identifier (slug) — pass this as `institution_id` when creating payment details"},"payment_method_id":{"type":"integer","format":"int64","description":"Numeric payment method id. Retail clients submit this (with `payment_network_id`) when saving a payment detail or creating a trade. Omitted for the `imps-neft-india` directory, which has no `payment_methods` rows."},"payment_network_id":{"type":"integer","format":"int64","description":"Numeric id of the queried rail (the `rail` query parameter), submitted alongside `payment_method_id`. Omitted for the `imps-neft-india` directory."},"name":{"type":"string","description":"Institution name"},"type":{"type":"string","description":"Broad institution category (e.g., BANK, WALLET)"},"institution_type":{"type":"string","description":"Fine-grained institution type slug (e.g. `mobile-money`, `mobile-wallets`, `traditional-banks`, `digital-banks`). On a multi-format rail, match this to the rail's `formats[].institution_types` to select the right field set."},"code":{"type":"string","description":"Corridor-specific institution code: the 4-letter IFSC bank prefix for `imps-neft-india`, the NIBSS code for `nip-nigeria`, the reference BIC for `iban-sepa`, etc. Omitted when the institution has no code. For SEPA the BIC is a reference identifier and may not be unique per institution — do not rely on it as a routing BIC."},"branch":{"$ref":"#/components/schemas/InstitutionBranch"},"supported_rails":{"type":"array","items":{"type":"string"},"description":"List of payment rail slugs this institution supports"}}},"InstitutionBranch":{"type":"object","nullable":true,"properties":{"code":{"type":"string","description":"Branch code (e.g., IFSC, sort code)"},"name":{"type":"string","description":"Branch name"},"address":{"type":"string","description":"Branch address"}}}}}}
```

## The Pagination object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"Pagination":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of items"},"limit":{"type":"integer","description":"Items per page"},"offset":{"type":"integer","description":"Current offset"}}}}}}
```

## The InstitutionsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"InstitutionsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","required":["institutions","pagination"],"properties":{"institutions":{"type":"array","items":{"$ref":"#/components/schemas/Institution"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"Institution":{"type":"object","properties":{"id":{"type":"string","description":"Institution identifier (slug) — pass this as `institution_id` when creating payment details"},"payment_method_id":{"type":"integer","format":"int64","description":"Numeric payment method id. Retail clients submit this (with `payment_network_id`) when saving a payment detail or creating a trade. Omitted for the `imps-neft-india` directory, which has no `payment_methods` rows."},"payment_network_id":{"type":"integer","format":"int64","description":"Numeric id of the queried rail (the `rail` query parameter), submitted alongside `payment_method_id`. Omitted for the `imps-neft-india` directory."},"name":{"type":"string","description":"Institution name"},"type":{"type":"string","description":"Broad institution category (e.g., BANK, WALLET)"},"institution_type":{"type":"string","description":"Fine-grained institution type slug (e.g. `mobile-money`, `mobile-wallets`, `traditional-banks`, `digital-banks`). On a multi-format rail, match this to the rail's `formats[].institution_types` to select the right field set."},"code":{"type":"string","description":"Corridor-specific institution code: the 4-letter IFSC bank prefix for `imps-neft-india`, the NIBSS code for `nip-nigeria`, the reference BIC for `iban-sepa`, etc. Omitted when the institution has no code. For SEPA the BIC is a reference identifier and may not be unique per institution — do not rely on it as a routing BIC."},"branch":{"$ref":"#/components/schemas/InstitutionBranch"},"supported_rails":{"type":"array","items":{"type":"string"},"description":"List of payment rail slugs this institution supports"}}},"InstitutionBranch":{"type":"object","nullable":true,"properties":{"code":{"type":"string","description":"Branch code (e.g., IFSC, sort code)"},"name":{"type":"string","description":"Branch name"},"address":{"type":"string","description":"Branch address"}}},"Pagination":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of items"},"limit":{"type":"integer","description":"Items per page"},"offset":{"type":"integer","description":"Current offset"}}}}}}
```

## The SupportedPair object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"SupportedPair":{"type":"object","properties":{"crypto_currency_code":{"type":"string","description":"Cryptocurrency code (e.g., USDC, BTC)"},"fiat_currency_code":{"type":"string","description":"Fiat currency code (e.g., USD, EUR)"},"type":{"type":"string","enum":["BUY","SELL"],"description":"Trading pair type - BUY (fiat to crypto) or SELL (crypto to fiat)"}}}}}}
```

## The SupportedPairsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"SupportedPairsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SupportedPair"}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"SupportedPair":{"type":"object","properties":{"crypto_currency_code":{"type":"string","description":"Cryptocurrency code (e.g., USDC, BTC)"},"fiat_currency_code":{"type":"string","description":"Fiat currency code (e.g., USD, EUR)"},"type":{"type":"string","enum":["BUY","SELL"],"description":"Trading pair type - BUY (fiat to crypto) or SELL (crypto to fiat)"}}}}}}
```

## The CreateUserResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"CreateUserResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"user_uuid":{"type":"string","format":"uuid","description":"Public UUID of the user. Use this value in all subsequent API calls."},"email":{"type":"string","format":"email"},"kyc_status":{"type":"string","enum":["NOT_INITIATED","IN_PROGRESS","UNDER_REVIEW","VERIFIED","VERIFICATION_REJECTED"]},"created_at":{"type":"string","format":"date-time"}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}}}}}
```

## The GetUserResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"GetUserResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"user_uuid":{"type":"string","format":"uuid","description":"Public UUID of the user. Use this value in all subsequent API calls."},"email":{"type":"string","format":"email"},"created_at":{"type":"string","format":"date-time"},"kyc":{"type":"object","properties":{"status":{"type":"string","enum":["NOT_INITIATED","IN_PROGRESS","UNDER_REVIEW","VERIFIED","VERIFICATION_REJECTED"],"description":"Current partner-facing KYC status. `UNDER_REVIEW` means the customer is under\nmanual compliance review — not yet cleared, and unlike `IN_PROGRESS` (automated,\nusually a couple of minutes) it can take up to 24 hours. The internal review\nstate name is never exposed.\n"},"kyc_method":{"type":"string","enum":["direct_data","external_token","handoff"],"description":"KYC submission method used"},"first_name":{"type":"string","description":"User's first name from KYC data"},"last_name":{"type":"string","description":"User's last name from KYC data"},"country_code":{"type":"string","description":"User's country code (ISO 2-letter) from KYC data"},"rejection_reason":{"type":"string","nullable":true,"description":"Reason for KYC rejection (if status is VERIFICATION_REJECTED)"},"updated_at":{"type":"string","format":"date-time","description":"Last update timestamp for KYC status"}}},"payment_profiles":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"rail":{"type":"string"},"currency":{"type":"string"},"institution_name":{"type":"string","nullable":true},"institution_id":{"type":"string","nullable":true,"description":"Institution identifier (our internal slug)"},"details":{"type":"object","additionalProperties":true}}}}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}}}}}
```

## The KYCSubmitDirectData object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"KYCSubmitDirectData":{"type":"object","required":["method","data"],"properties":{"method":{"type":"string","enum":["direct_data"]},"data":{"type":"object","required":["pii"],"properties":{"pii":{"type":"object","required":["first_name","last_name","country_code"],"properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"country_code":{"type":"string"},"dob":{"type":"string","format":"date","description":"Date of birth (YYYY-MM-DD). Optional, but when provided it must be a real date in the past and indicate an age of at least 18. A future, malformed, or under-18 date is rejected with HTTP 400 (see error responses below)."},"middle_name":{"type":"string"},"phone_number":{"type":"string","description":"E.164 format"},"id_number":{"type":"string","description":"Government identity/registry number. Enables the **NIN/BVN + selfie** verification option: instead of uploading an ID-document image, the customer proves identity with this number plus a live selfie (no ID photo). AiPrise validates the number against the national registry (NIN->NIMC, BVN->NIBSS) and face-matches the selfie against the registry photo. After this submission, upload only a `selfie` (document_type=selfie) to the documents endpoint - verification starts automatically. Supported for the registry number types listed under `id_type`, in the countries those registries cover."},"id_type":{"type":"string","description":"Registry type for `id_number`. For the NIN/BVN + selfie option it must be one of the listed registry types (image-only IDs such as passport/driver licence are NOT accepted - those use the ID-image flow). An unsupported/missing value returns `400 { \"error_key\": \"invalid_identity_number_type\" }`.","enum":["NIN","NIN_SLIP","BVN","NATIONAL_ID","VOTER_ID","TAX_ID","KENYA_KRA_PIN"]},"address":{"type":"string","description":"Street address"},"city":{"type":"string","description":"City. Required for EUR/AUD/GBP offramp (auto-populates into payment details)."},"postal_code":{"type":"string"}}}}}}}}}}
```

## The KYCSubmitExternalToken object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"KYCSubmitExternalToken":{"type":"object","required":["method","data"],"properties":{"method":{"type":"string","enum":["external_token"]},"data":{"type":"object","required":["provider"],"description":"Provider-specific fields:\n- SumSub: requires `token` (SumSub share token)\n- Persona: requires `inquiry_id` (Persona inquiry ID, e.g. inq_xxx)\n","properties":{"provider":{"type":"string","enum":["sumsub","persona"],"description":"KYC provider name"},"token":{"type":"string","description":"SumSub share token (required when provider=sumsub)"},"inquiry_id":{"type":"string","description":"Persona inquiry ID (required when provider=persona). This is the inq_xxx identifier returned by Persona when the inquiry was created."}}}}}}}}
```

## The KYCSubmitHandoff object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"KYCSubmitHandoff":{"type":"object","required":["method","data"],"properties":{"method":{"type":"string","enum":["handoff"]},"data":{"type":"object","properties":{"pii":{"type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"country_code":{"type":"string"}}}}}}}}}}
```

## The KYCSubmitResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"KYCSubmitResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"kyc_status":{"type":"string","enum":["NOT_INITIATED","IN_PROGRESS","UNDER_REVIEW","VERIFIED","VERIFICATION_REJECTED"],"description":"A submission may asynchronously move to `UNDER_REVIEW` (manual compliance review,\nup to 24 hours) via the status endpoint and the `user.kyc.updated` webhook — distinct\nfrom `IN_PROGRESS` (automated, usually a couple of minutes). Treat both as not cleared.\n"},"message":{"type":"string"},"verification_url":{"type":"string","format":"uri","nullable":true,"description":"Verification URL (only returned for handoff method)"}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}}}}}
```

## The VerificationStatusResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"VerificationStatusResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"status":{"type":"string","enum":["NOT_INITIATED","PENDING_DOCUMENTS","IN_PROGRESS","UNDER_REVIEW","VERIFIED","FAILED","VERIFICATION_REJECTED"],"description":"Current verification status.\n\n- `NOT_INITIATED` — no verification started\n- `PENDING_DOCUMENTS` — awaiting customer document submission\n- `IN_PROGRESS` — automated verification underway (usually a couple of minutes); treat as not cleared yet\n- `UNDER_REVIEW` — under manual compliance review; the customer is being reviewed by our team and is NOT yet cleared. Unlike `IN_PROGRESS` (automated, usually a couple of minutes), this can take up to 24 hours. Keep polling; treat as not cleared.\n- `VERIFIED` — approved\n- `FAILED` — terminal error\n- `VERIFICATION_REJECTED` — completed with rejection\n"},"verification_url":{"type":"string","format":"uri","nullable":true,"description":"Verification URL (for handoff method)"},"verification_seconds_left":{"type":"integer","nullable":true,"description":"Seconds remaining for verification session (if applicable)"},"provider_messages":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Status messages or rejection reasons"}}}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}}}}}
```

## The CreatePaymentDetailsRequest object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"CreatePaymentDetailsRequest":{"description":"Conditional request schema for partner payment details. `institution_id` may be omitted for `iban-sepa`, `nip-nigeria`, and `imps-neft-india`.","oneOf":[{"$ref":"#/components/schemas/CreatePaymentDetailsRequestInstitutionOptional"},{"$ref":"#/components/schemas/CreatePaymentDetailsRequestInstitutionRequired"}]},"CreatePaymentDetailsRequestInstitutionOptional":{"allOf":[{"$ref":"#/components/schemas/CreatePaymentDetailsRequestBase"},{"type":"object","properties":{"rail":{"type":"string","enum":["iban-sepa","nip-nigeria","imps-neft-india"],"description":"Payment rail identifier for rails that support omission of `institution_id`"},"institution_id":{"type":"string","description":"Optional for `iban-sepa`, `nip-nigeria`, and `imps-neft-india`.\nFor `iban-sepa`/`nip-nigeria`, if omitted the backend assigns the generic `other-bank` payment method automatically.\nFor `imps-neft-india` it is ignored entirely (resolves to the generic `imps-or-neft-transfer` method); the bank is taken from `details.bank_name` + `details.ifsc_code`.\n"}}}]},"CreatePaymentDetailsRequestBase":{"type":"object","required":["currency","rail","details"],"properties":{"currency":{"type":"string","description":"ISO 3-letter fiat currency code (e.g., INR, EUR, USD)"},"rail":{"type":"string","description":"Payment rail identifier (e.g., \"upi-india\", \"iban-sepa\", \"imps-neft-india\")"},"details":{"type":"object","description":"Payment details object with fields specific to the payment network (e.g., account_number, vpa, iban, full_name)","additionalProperties":true},"country_code":{"type":"string","description":"Optional ISO 2-letter country code"}}},"CreatePaymentDetailsRequestInstitutionRequired":{"allOf":[{"$ref":"#/components/schemas/CreatePaymentDetailsRequestBase"},{"type":"object","required":["institution_id"],"properties":{"rail":{"type":"string","description":"Payment rail identifier for rails that require institution selection","not":{"enum":["iban-sepa","nip-nigeria","imps-neft-india"]}},"institution_id":{"type":"string","description":"Institution (payment method) identifier - our internal slug (e.g., \"wise\", \"hdfc-bank\", \"revolut\", \"uala\", \"banco-santander-argentina\").\nRequired for all rails except `iban-sepa`, `nip-nigeria`, and `imps-neft-india`.\nIf omitted for any other rail, the API returns a validation error.\nFor rails with multiple formats (e.g., CVU/CBU), the format is automatically determined based on the payment method type of this institution.\n"}}}]}}}}
```

## The CreatePaymentDetailsRequestBase object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"CreatePaymentDetailsRequestBase":{"type":"object","required":["currency","rail","details"],"properties":{"currency":{"type":"string","description":"ISO 3-letter fiat currency code (e.g., INR, EUR, USD)"},"rail":{"type":"string","description":"Payment rail identifier (e.g., \"upi-india\", \"iban-sepa\", \"imps-neft-india\")"},"details":{"type":"object","description":"Payment details object with fields specific to the payment network (e.g., account_number, vpa, iban, full_name)","additionalProperties":true},"country_code":{"type":"string","description":"Optional ISO 2-letter country code"}}}}}}
```

## The CreatePaymentDetailsRequestInstitutionOptional object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"CreatePaymentDetailsRequestInstitutionOptional":{"allOf":[{"$ref":"#/components/schemas/CreatePaymentDetailsRequestBase"},{"type":"object","properties":{"rail":{"type":"string","enum":["iban-sepa","nip-nigeria","imps-neft-india"],"description":"Payment rail identifier for rails that support omission of `institution_id`"},"institution_id":{"type":"string","description":"Optional for `iban-sepa`, `nip-nigeria`, and `imps-neft-india`.\nFor `iban-sepa`/`nip-nigeria`, if omitted the backend assigns the generic `other-bank` payment method automatically.\nFor `imps-neft-india` it is ignored entirely (resolves to the generic `imps-or-neft-transfer` method); the bank is taken from `details.bank_name` + `details.ifsc_code`.\n"}}}]},"CreatePaymentDetailsRequestBase":{"type":"object","required":["currency","rail","details"],"properties":{"currency":{"type":"string","description":"ISO 3-letter fiat currency code (e.g., INR, EUR, USD)"},"rail":{"type":"string","description":"Payment rail identifier (e.g., \"upi-india\", \"iban-sepa\", \"imps-neft-india\")"},"details":{"type":"object","description":"Payment details object with fields specific to the payment network (e.g., account_number, vpa, iban, full_name)","additionalProperties":true},"country_code":{"type":"string","description":"Optional ISO 2-letter country code"}}}}}}
```

## The CreatePaymentDetailsRequestInstitutionRequired object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"CreatePaymentDetailsRequestInstitutionRequired":{"allOf":[{"$ref":"#/components/schemas/CreatePaymentDetailsRequestBase"},{"type":"object","required":["institution_id"],"properties":{"rail":{"type":"string","description":"Payment rail identifier for rails that require institution selection","not":{"enum":["iban-sepa","nip-nigeria","imps-neft-india"]}},"institution_id":{"type":"string","description":"Institution (payment method) identifier - our internal slug (e.g., \"wise\", \"hdfc-bank\", \"revolut\", \"uala\", \"banco-santander-argentina\").\nRequired for all rails except `iban-sepa`, `nip-nigeria`, and `imps-neft-india`.\nIf omitted for any other rail, the API returns a validation error.\nFor rails with multiple formats (e.g., CVU/CBU), the format is automatically determined based on the payment method type of this institution.\n"}}}]},"CreatePaymentDetailsRequestBase":{"type":"object","required":["currency","rail","details"],"properties":{"currency":{"type":"string","description":"ISO 3-letter fiat currency code (e.g., INR, EUR, USD)"},"rail":{"type":"string","description":"Payment rail identifier (e.g., \"upi-india\", \"iban-sepa\", \"imps-neft-india\")"},"details":{"type":"object","description":"Payment details object with fields specific to the payment network (e.g., account_number, vpa, iban, full_name)","additionalProperties":true},"country_code":{"type":"string","description":"Optional ISO 2-letter country code"}}}}}}
```

## The PaymentProfile object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PaymentProfile":{"type":"object","properties":{"payment_details_id":{"type":"string","description":"Payment details ID. Use this value in offramp/onramp quote requests."},"rail":{"type":"string","description":"Payment rail identifier"},"currency":{"type":"string","description":"Fiat currency code"},"institution_name":{"type":"string","nullable":true,"description":"Institution (bank) name if applicable"},"institution_id":{"type":"string","nullable":true,"description":"Institution identifier (our internal slug)"},"details":{"type":"object","description":"Payment details (account number, VPA, IBAN, etc.)","additionalProperties":true}}}}}}
```

## The CreatePaymentDetailsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"CreatePaymentDetailsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PaymentProfile"}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PaymentProfile":{"type":"object","properties":{"payment_details_id":{"type":"string","description":"Payment details ID. Use this value in offramp/onramp quote requests."},"rail":{"type":"string","description":"Payment rail identifier"},"currency":{"type":"string","description":"Fiat currency code"},"institution_name":{"type":"string","nullable":true,"description":"Institution (bank) name if applicable"},"institution_id":{"type":"string","nullable":true,"description":"Institution identifier (our internal slug)"},"details":{"type":"object","description":"Payment details (account number, VPA, IBAN, etc.)","additionalProperties":true}}}}}}
```

## The GetPaymentDetailResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"GetPaymentDetailResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PaymentProfile"}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PaymentProfile":{"type":"object","properties":{"payment_details_id":{"type":"string","description":"Payment details ID. Use this value in offramp/onramp quote requests."},"rail":{"type":"string","description":"Payment rail identifier"},"currency":{"type":"string","description":"Fiat currency code"},"institution_name":{"type":"string","nullable":true,"description":"Institution (bank) name if applicable"},"institution_id":{"type":"string","nullable":true,"description":"Institution identifier (our internal slug)"},"details":{"type":"object","description":"Payment details (account number, VPA, IBAN, etc.)","additionalProperties":true}}}}}}
```

## The GetPaymentDetailsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"GetPaymentDetailsResponse":{"allOf":[{"$ref":"#/components/schemas/APIResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PaymentProfile"},"description":"List of all payment profiles for the user"}}}]},"APIResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Indicates if the request was successful"},"data":{"description":"Response data (structure varies by endpoint) or error information if success is false"}}},"PaymentProfile":{"type":"object","properties":{"payment_details_id":{"type":"string","description":"Payment details ID. Use this value in offramp/onramp quote requests."},"rail":{"type":"string","description":"Payment rail identifier"},"currency":{"type":"string","description":"Fiat currency code"},"institution_name":{"type":"string","nullable":true,"description":"Institution (bank) name if applicable"},"institution_id":{"type":"string","nullable":true,"description":"Institution identifier (our internal slug)"},"details":{"type":"object","description":"Payment details (account number, VPA, IBAN, etc.)","additionalProperties":true}}}}}}
```

## The SlippageTolerance object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"SlippageTolerance":{"type":"object","properties":{"type":{"type":"string","enum":["percent","amount","fiat"],"description":"Tolerance type:\n- `percent` — percentage of the quoted rate (e.g., 1.5 = 1.5%)\n- `amount` — absolute fiat amount (e.g., 50.00 = 50 NGN)\n- `fiat` — alias of `amount`\n"},"value":{"type":"number","format":"double","description":"Tolerance value (percentage or fiat amount depending on type)"}}}}}}
```

## The LiquidityPair object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"LiquidityPair":{"type":"object","required":["crypto_currency_code","fiat_currency_code","trade_type","min_amount","max_amount","min_amount_usd","max_amount_usd","payment_methods"],"properties":{"crypto_currency_code":{"type":"string","description":"Cryptocurrency code (e.g. USDC, BTC)"},"fiat_currency_code":{"type":"string","description":"Fiat currency code (ISO 4217)"},"trade_type":{"type":"string","enum":["BUY","SELL"],"description":"Partner-facing trade direction.\n`SELL` — partner sells crypto and receives fiat (off-ramp).\n`BUY` — partner buys crypto by sending fiat (on-ramp).\n"},"min_amount":{"type":"number","format":"double","description":"Minimum fiat amount accepted across all eligible offers, in the pair's native fiat currency"},"max_amount":{"type":"number","format":"double","description":"Maximum fiat amount accepted across all eligible offers, in the pair's native fiat currency"},"min_amount_usd":{"type":"number","format":"double","description":"Minimum fiat amount converted to USD"},"max_amount_usd":{"type":"number","format":"double","description":"Maximum fiat amount converted to USD"},"payment_methods":{"type":"array","items":{"type":"string"},"description":"Deduplicated, alphabetically sorted list of payment method slugs available for this pair"}}}}}}
```

## The PartnerPriceEstimateRequest object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerPriceEstimateRequest":{"type":"object","required":["crypto_currency","fiat_currency"],"oneOf":[{"required":["crypto_amount"]},{"required":["fiat_amount"]}],"properties":{"crypto_currency":{"type":"string","description":"Cryptocurrency code (e.g., USDT, USDC)"},"fiat_currency":{"type":"string","description":"Target fiat currency code (e.g., NGN, USD, EUR)"},"crypto_amount":{"type":"string","description":"Crypto amount to estimate from. Provide exactly one of crypto_amount or fiat_amount."},"fiat_amount":{"type":"string","description":"Fiat amount to estimate from. Provide exactly one of crypto_amount or fiat_amount."},"payment_method_slug":{"type":"string","description":"Optional payment method slug filter to narrow routing"},"payment_network_slug":{"type":"string","description":"Optional payment network slug filter to narrow routing"},"provider_scope":{"type":"string","description":"Optional provider scope filter; defaults to all","enum":["all","licensed_only","p2p_only"]},"country_code":{"type":"string","description":"Optional ISO country code (alpha-2) used for fee/routing context"},"partner_fee_pct":{"type":"string","pattern":"^[0-9]+(\\.[0-9]+)?$","description":"Optional per-order markup you charge your end user, applied on the same crypto base\nas the platform fee (non-compounding). Percent units: `1` means 1%. Omitting the field\nfrom this request payload — or sending `0` — means no markup; the markup cannot be\nnegative. Fiat-anchored offramp orders have no cap — the deposit grows to cover the\nmarkup. Every other case hits a formula domain bound, not a business cap: onramp\nrejects `partner_fee_pct >= 100` (the withheld or grossed-up fee would consume the\nentire buyer amount, leaving nothing delivered); crypto-anchored offramp rejects it\nwhen the fee would consume the entire pinned deposit.\n\nNote: you send this value as a JSON **string**, but it round-trips as a JSON **number**\nin `fee_breakdown.partner_fee_pct` and in the webhook `data.partner_fee_pct` field —\ndon't parse those as strings.\n\n**Rejection reference:**\n\n| Rejection | `error.code` | HTTP status |\n|---|---|---|\n| `partner_fee_pct` is non-numeric, negative, `NaN`, or `Inf` | `INVALID_REQUEST` | 400 |\n| Onramp: fee would consume the entire buyer amount (`partner_fee_pct >= 100`) | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: fee would consume the entire pinned deposit | `INVALID_REQUEST` | 400 |\n| Fiat-anchored offramp: the computed fee is not a finite amount | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: the carved fee-exclusive gross falls outside the matched offer's min/max | `NO_OFFERS_AVAILABLE` | 409 |\n","default":"0"}}}}}}
```

## The PartnerPriceEstimateResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerPriceEstimateResponse":{"type":"object","properties":{"rate":{"type":"string","description":"Indicative crypto-to-fiat exchange rate"},"crypto_amount":{"type":"string","description":"Estimated total crypto amount — the fee-exclusive gross: the user's amount plus the platform\nfee. Partner-fee-exclusive, except for a crypto-anchored onramp estimate (request declares\n`crypto_amount`): there the amount is sized up so the delivered amount stays exact after the\npartner fee is withheld, so this field includes the markup. A crypto-anchored offramp\nestimate carves both fees out of the pinned deposit instead, so this field and\n`fee_breakdown.platform_fee` both shrink relative to a no-markup request, even though the\nmarkup itself stays out of this field. Every other case is unaffected — the markup is\nreported separately in `fee_breakdown.partner_fee`.\n\nOn-ramp: the end user would receive `crypto_amount − fee_breakdown.platform_fee −\nfee_breakdown.partner_fee` for a fiat-anchored estimate, or exactly this `crypto_amount` for a\ncrypto-anchored estimate. Off-ramp: the amount you would actually fund at initiation is\n`crypto_amount + fee_breakdown.partner_fee`.\n"},"fiat_amount":{"type":"string","description":"Estimated fiat amount"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"payment_method_slug":{"type":"string","description":"Matched payment method slug used for estimate"},"payment_network_slug":{"type":"string","description":"Matched payment network slug used for estimate"},"provider_scope":{"type":"string","description":"Applied provider scope"},"is_indicative":{"type":"boolean","description":"True when response is an estimate and not a reserving quote"}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}}}}}
```

## The PartnerRecipientDestinationInput object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerRecipientDestinationInput":{"type":"object","required":["country_code","currency","rail","institution_id","details"],"properties":{"country_code":{"type":"string","minLength":2,"maxLength":2},"currency":{"type":"string","minLength":3,"maxLength":3},"rail":{"type":"string"},"institution_id":{"type":"string","description":"Institution identifier returned by the supported-institutions endpoint."},"details":{"type":"object","additionalProperties":true,"description":"Rail-specific fields validated using the selected payment network configuration."}}}}}}
```

## The PartnerCreateRecipientRequest object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerCreateRecipientRequest":{"type":"object","required":["recipient_name","recipient_country"],"properties":{"recipient_kind":{"type":"string","enum":["individual","business"],"default":"individual"},"recipient_name":{"type":"string"},"recipient_native_name":{"type":"string"},"recipient_country":{"type":"string","minLength":2,"maxLength":2},"recipient_id_type":{"type":"string"},"recipient_id_number":{"type":"string","writeOnly":true},"recipient_business_registration_number":{"type":"string","writeOnly":true}}}}}}
```

## The PartnerUpdateRecipientRequest object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerUpdateRecipientRequest":{"type":"object","minProperties":1,"properties":{"recipient_name":{"type":"string"},"recipient_native_name":{"type":"string"},"recipient_country":{"type":"string","minLength":2,"maxLength":2},"recipient_id_type":{"type":"string"},"recipient_id_number":{"type":"string","writeOnly":true},"recipient_business_registration_number":{"type":"string","writeOnly":true}}}}}}
```

## The PartnerRecipient object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerRecipient":{"type":"object","required":["id","kind","recipient_name","recipient_country","lifecycle","version","screening_status","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["individual","business"]},"recipient_name":{"type":"string"},"recipient_native_name":{"type":"string","nullable":true},"recipient_country":{"type":"string"},"recipient_id_type":{"type":"string","nullable":true},"recipient_id_number":{"type":"string","nullable":true,"description":"Masked partner-visible identity value; full value is never returned."},"recipient_business_registration_number":{"type":"string","nullable":true,"description":"Masked when returned."},"lifecycle":{"type":"string","enum":["draft","active","archived"]},"version":{"type":"integer","minimum":1},"screening_status":{"type":"string","enum":["pending","cleared","hit","manual_review"]},"screened_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}}}
```

## The PartnerRecipientDestination object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerRecipientDestination":{"type":"object","required":["id","recipient_id","payment_method_id","payment_network_id","country_code","currency","version","lifecycle","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"payment_method_id":{"type":"integer","format":"int64"},"payment_network_id":{"type":"integer","format":"int64"},"country_code":{"type":"string","minLength":2,"maxLength":2},"currency":{"type":"string"},"details":{"type":"object","additionalProperties":true,"description":"The rail's own fields, as submitted. On reads, values that identify an account or a person are masked to last-4 (`account_number`, `bank_account_number`, `iban`, `card_number`, `id_number`, `national_id`, `tax_id`, `mobile_number`, `phone_number`, `msisdn`); everything else — names, `bank_name`, `province`, `branch`, routing codes, `beneficiary_type` — is returned in full so destinations stay distinguishable."},"version":{"type":"integer","minimum":1},"lifecycle":{"type":"string","enum":["active","archived"]},"created_at":{"type":"string","format":"date-time"}}}}}}
```

## The PartnerRecipientContext object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerRecipientContext":{"type":"object","required":["sender_id","recipient_id","recipient_destination_id","sender_recipient_relationship","purpose_of_payment","screening_status"],"properties":{"sender_id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"recipient_destination_id":{"type":"string","format":"uuid"},"sender_recipient_relationship":{"type":"string"},"purpose_of_payment":{"type":"string"},"purpose_details":{"type":"string"},"screening_status":{"type":"string"}}}}}}
```

## The PartnerComplianceCase object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerComplianceCase":{"type":"object","required":["case_id","status","risk_decision","requires_review"],"description":"The compliance record for a third-party payout. In v1 the controls run BEFORE the order is\ncreated, so a payout that fails one is rejected with an error rather than returned as a held\norder — see the error codes in the Third-party payouts guide.\n","properties":{"case_id":{"type":"string","format":"uuid","description":"Identifier of the compliance record — the payout's `quote_id`, so a partner and a Unigox\noperator refer to the same record.\n"},"status":{"type":"string","enum":["authorized"],"description":"v1 emits `authorized`; a rejected payout returns an error instead."},"risk_decision":{"type":"string","enum":["allow"],"description":"v1 emits `allow`; a blocking decision surfaces as an error, not a status."},"requires_review":{"type":"boolean","description":"Always false in v1. Operator review happens Unigox-side over the same record."}}}}}}
```

## The PartnerRecipientResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerRecipientResponse":{"type":"object","required":["recipient"],"properties":{"recipient":{"$ref":"#/components/schemas/PartnerRecipient"},"recipient_destination":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientDestination"}],"nullable":true}}},"PartnerRecipient":{"type":"object","required":["id","kind","recipient_name","recipient_country","lifecycle","version","screening_status","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["individual","business"]},"recipient_name":{"type":"string"},"recipient_native_name":{"type":"string","nullable":true},"recipient_country":{"type":"string"},"recipient_id_type":{"type":"string","nullable":true},"recipient_id_number":{"type":"string","nullable":true,"description":"Masked partner-visible identity value; full value is never returned."},"recipient_business_registration_number":{"type":"string","nullable":true,"description":"Masked when returned."},"lifecycle":{"type":"string","enum":["draft","active","archived"]},"version":{"type":"integer","minimum":1},"screening_status":{"type":"string","enum":["pending","cleared","hit","manual_review"]},"screened_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"PartnerRecipientDestination":{"type":"object","required":["id","recipient_id","payment_method_id","payment_network_id","country_code","currency","version","lifecycle","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"payment_method_id":{"type":"integer","format":"int64"},"payment_network_id":{"type":"integer","format":"int64"},"country_code":{"type":"string","minLength":2,"maxLength":2},"currency":{"type":"string"},"details":{"type":"object","additionalProperties":true,"description":"The rail's own fields, as submitted. On reads, values that identify an account or a person are masked to last-4 (`account_number`, `bank_account_number`, `iban`, `card_number`, `id_number`, `national_id`, `tax_id`, `mobile_number`, `phone_number`, `msisdn`); everything else — names, `bank_name`, `province`, `branch`, routing codes, `beneficiary_type` — is returned in full so destinations stay distinguishable."},"version":{"type":"integer","minimum":1},"lifecycle":{"type":"string","enum":["active","archived"]},"created_at":{"type":"string","format":"date-time"}}}}}}
```

## The PartnerRecipientListResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerRecipientListResponse":{"type":"object","required":["recipients","total","limit","offset"],"properties":{"recipients":{"type":"array","items":{"$ref":"#/components/schemas/PartnerRecipient"}},"total":{"type":"integer","minimum":0},"limit":{"type":"integer","minimum":1},"offset":{"type":"integer","minimum":0}}},"PartnerRecipient":{"type":"object","required":["id","kind","recipient_name","recipient_country","lifecycle","version","screening_status","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["individual","business"]},"recipient_name":{"type":"string"},"recipient_native_name":{"type":"string","nullable":true},"recipient_country":{"type":"string"},"recipient_id_type":{"type":"string","nullable":true},"recipient_id_number":{"type":"string","nullable":true,"description":"Masked partner-visible identity value; full value is never returned."},"recipient_business_registration_number":{"type":"string","nullable":true,"description":"Masked when returned."},"lifecycle":{"type":"string","enum":["draft","active","archived"]},"version":{"type":"integer","minimum":1},"screening_status":{"type":"string","enum":["pending","cleared","hit","manual_review"]},"screened_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}}}
```

## The PartnerRecipientDestinationsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerRecipientDestinationsResponse":{"type":"object","required":["recipient_destinations"],"properties":{"recipient_destinations":{"type":"array","items":{"$ref":"#/components/schemas/PartnerRecipientDestination"}}}},"PartnerRecipientDestination":{"type":"object","required":["id","recipient_id","payment_method_id","payment_network_id","country_code","currency","version","lifecycle","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"payment_method_id":{"type":"integer","format":"int64"},"payment_network_id":{"type":"integer","format":"int64"},"country_code":{"type":"string","minLength":2,"maxLength":2},"currency":{"type":"string"},"details":{"type":"object","additionalProperties":true,"description":"The rail's own fields, as submitted. On reads, values that identify an account or a person are masked to last-4 (`account_number`, `bank_account_number`, `iban`, `card_number`, `id_number`, `national_id`, `tax_id`, `mobile_number`, `phone_number`, `msisdn`); everything else — names, `bank_name`, `province`, `branch`, routing codes, `beneficiary_type` — is returned in full so destinations stay distinguishable."},"version":{"type":"integer","minimum":1},"lifecycle":{"type":"string","enum":["active","archived"]},"created_at":{"type":"string","format":"date-time"}}}}}}
```

## The PartnerQuoteRequest object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerQuoteRequest":{"type":"object","required":["user_uuid","crypto_currency","fiat_currency"],"allOf":[{"oneOf":[{"required":["crypto_amount"]},{"required":["fiat_amount"]}]},{"oneOf":[{"required":["payment_details_id"]},{"required":["sender_id","recipient_id","recipient_destination_id","sender_recipient_relationship","purpose_of_payment"]}]}],"properties":{"user_uuid":{"type":"string","format":"uuid","description":"Public UUID of the end-user (returned by `POST /partner/users` as `id`)"},"payment_details_id":{"type":"integer","format":"int64","description":"Required for a self-payout. MUST be omitted for a third-party payout, which is routed by `recipient_destination_id`. Sending both is rejected with `400 INVALID_REQUEST` — the two modes are mutually exclusive and neither silently wins."},"sender_id":{"type":"string","format":"uuid","description":"Canonical KYC subject who is funding the third-party payout. Required for third-party\npayout and must equal the real sender represented by `user_uuid`.\n"},"recipient_id":{"type":"string","format":"uuid","description":"Partner-owned recipient returned by `POST /partner/recipients`."},"recipient_destination_id":{"type":"string","format":"uuid","description":"Active destination owned by `recipient_id`; replaces `payment_details_id` for third-party payout."},"sender_recipient_relationship":{"type":"string","minLength":1,"description":"Payment-level relationship between the real sender and recipient, for example `supplier` or `family`."},"purpose_of_payment":{"type":"string","minLength":1,"description":"Canonical payment purpose used for compliance and translated only at the vendor boundary."},"purpose_details":{"type":"string","description":"Optional invoice or free-text clarification of the payment purpose."},"crypto_currency":{"type":"string","description":"Cryptocurrency code (e.g., USDT, USDC)"},"crypto_amount":{"type":"string","description":"Amount of crypto to sell (standard units, e.g., \"100.00\" for 100 USDT).\nProvide exactly one of `crypto_amount` or `fiat_amount`.\nWhen provided, the order is **crypto-anchored** — exact crypto cost is guaranteed, fiat payout varies by rate at settlement.\n"},"fiat_amount":{"type":"string","description":"Exact fiat amount the recipient will receive (e.g., \"500000\" for 500,000 NGN).\nProvide exactly one of `crypto_amount` or `fiat_amount`.\nWhen provided, the order is **fiat-anchored** — exact fiat delivery is guaranteed, crypto cost may vary within a 1.20× safety bound.\nFor integer-minor-unit currencies (NGN, UGX, TZS, KES, GHS), must be a whole number.\n"},"fiat_currency":{"type":"string","description":"Target fiat currency code (e.g., NGN, USD, EUR)"},"rail":{"type":"string","description":"Optional rail hint; must be compatible with payment details if provided"},"route":{"type":"string","description":"Optional route hint; must be compatible with payment details if provided"},"provider_scope":{"type":"string","description":"Optional provider scope filter; defaults to all","enum":["all","licensed_only","p2p_only"]},"slippage_tolerance":{"allOf":[{"$ref":"#/components/schemas/SlippageTolerance"}],"description":"Optional. Maximum tolerated price movement between `/quote` and `/initiate`,\nand between `/initiate` and vendor acceptance. When omitted, the default is\n`percent` / `0` — any rate movement returns `PRICE_CHANGED_REQUOTE` and the\npartner must request a fresh quote. Set this to opt in to silent execution\nfor small market movements.\n"},"has_fiat_settlement_notification":{"type":"boolean","description":"When `true`, only match liquidity providers that send fiat settlement\nnotifications. Orders from these providers complete automatically when\nfiat is delivered. When omitted or `false`, all providers are eligible.\n"},"partner_fee_pct":{"type":"string","pattern":"^[0-9]+(\\.[0-9]+)?$","description":"Optional per-order markup you charge your end user, applied on the same crypto base\nas the platform fee (non-compounding). Percent units: `1` means 1%. Omitting the field\nfrom this request payload — or sending `0` — means no markup; the markup cannot be\nnegative. Fiat-anchored offramp orders have no cap — the deposit grows to cover the\nmarkup. Every other case hits a formula domain bound, not a business cap: onramp\nrejects `partner_fee_pct >= 100` (the withheld or grossed-up fee would consume the\nentire buyer amount, leaving nothing delivered); crypto-anchored offramp rejects it\nwhen the fee would consume the entire pinned deposit.\n\nNote: you send this value as a JSON **string**, but it round-trips as a JSON **number**\nin `fee_breakdown.partner_fee_pct` and in the webhook `data.partner_fee_pct` field —\ndon't parse those as strings.\n\n**Rejection reference:**\n\n| Rejection | `error.code` | HTTP status |\n|---|---|---|\n| `partner_fee_pct` is non-numeric, negative, `NaN`, or `Inf` | `INVALID_REQUEST` | 400 |\n| Onramp: fee would consume the entire buyer amount (`partner_fee_pct >= 100`) | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: fee would consume the entire pinned deposit | `INVALID_REQUEST` | 400 |\n| Fiat-anchored offramp: the computed fee is not a finite amount | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: the carved fee-exclusive gross falls outside the matched offer's min/max | `NO_OFFERS_AVAILABLE` | 409 |\n","default":"0"}}},"SlippageTolerance":{"type":"object","properties":{"type":{"type":"string","enum":["percent","amount","fiat"],"description":"Tolerance type:\n- `percent` — percentage of the quoted rate (e.g., 1.5 = 1.5%)\n- `amount` — absolute fiat amount (e.g., 50.00 = 50 NGN)\n- `fiat` — alias of `amount`\n"},"value":{"type":"number","format":"double","description":"Tolerance value (percentage or fiat amount depending on type)"}}}}}}
```

## The PartnerQuoteResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerQuoteResponse":{"type":"object","properties":{"quote_id":{"type":"string","format":"uuid","description":"Unique quote identifier — use this to initiate the order"},"anchor_type":{"type":"string","enum":["crypto","fiat"],"description":"Indicates which amount is guaranteed at settlement.\n- `crypto` — `crypto_amount` is exact; `fiat_amount` may vary by the rate at settlement.\n- `fiat` — `fiat_amount` is exact; `crypto_amount` may vary within a 1.20× safety bound.\n"},"rate":{"type":"string","description":"Crypto-to-fiat exchange rate at quote time"},"crypto_amount":{"type":"string","description":"Total crypto amount — the fee-exclusive gross: the sell amount plus the platform fee.\nPartner-fee-exclusive: your markup is reported separately in `fee_breakdown.partner_fee` and\nis not added here. This is not the total you fund: the amount you actually fund at initiation\nis `crypto_amount + fee_breakdown.partner_fee` (see the transfer authorization endpoint).\n"},"fiat_amount":{"type":"string","description":"Fiat amount the recipient receives"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"payment_details_id":{"type":"integer","format":"int64","description":"Payment details ID used for this quote"},"payment_method_id":{"type":"integer","format":"int64","description":"Matched payment method ID"},"payment_network_id":{"type":"integer","format":"int64","description":"Matched payment network ID"},"expires_at":{"type":"string","format":"date-time","description":"Quote expiration timestamp (30 seconds from creation)"},"has_fiat_settlement_notification":{"type":"boolean","description":"Whether the matched liquidity provider sends fiat settlement notifications.\nWhen `true`, the order will complete automatically when fiat is delivered\nto the recipient. When `false`, you must call `confirm-fiat-received`.\nYou can call `confirm-fiat-received` at any time regardless of this value.\n"},"recipient_context":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientContext"}],"nullable":true,"description":"Immutable canonical recipient snapshot for a third-party quote; absent for self-payout."}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}},"PartnerRecipientContext":{"type":"object","required":["sender_id","recipient_id","recipient_destination_id","sender_recipient_relationship","purpose_of_payment","screening_status"],"properties":{"sender_id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"recipient_destination_id":{"type":"string","format":"uuid"},"sender_recipient_relationship":{"type":"string"},"purpose_of_payment":{"type":"string"},"purpose_details":{"type":"string"},"screening_status":{"type":"string"}}}}}}
```

## The PartnerInitiateRequest object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerInitiateRequest":{"type":"object","required":["quote_id"],"properties":{"quote_id":{"type":"string","format":"uuid","description":"Quote ID from the quote endpoint. Payment details are taken from the quote."},"reference_id":{"type":"string","description":"Optional partner-supplied external reference ID (e.g. your internal order ID). Stored on the order and echoed back in responses."}}}}}}
```

## The PartnerInitiateResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerInitiateResponse":{"type":"object","properties":{"order_id":{"type":"string","format":"uuid","description":"Unique order identifier for tracking"},"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"quote_id":{"type":"string","format":"uuid","description":"Quote ID that was consumed"},"reference_id":{"type":"string","nullable":true,"description":"Partner-supplied external reference ID (echoed from initiate request)"},"order_type":{"type":"string","enum":["offramp","onramp"],"description":"Direction of the order"},"amounts":{"$ref":"#/components/schemas/PartnerOrderAmounts"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/TimelineEntry"}},"created_at":{"type":"string","format":"date-time"},"recipient_context":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientContext"}],"nullable":true},"compliance":{"allOf":[{"$ref":"#/components/schemas/PartnerComplianceCase"}],"nullable":true,"description":"Durable case shown in Compliance admin for third-party payout."}}},"PartnerOrderStatus":{"type":"string","description":"Order status.\n\n**Off-ramp statuses (crypto → fiat):**\n- `created` — order created, quote locked\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_crypto_transfer_authorization` — partner must authorize crypto transfer via EIP-712 signature\n- `crypto_transfer_authorization_pending` — authorization submitted, awaiting on-chain confirmation\n- `crypto_received` — crypto locked in escrow\n- `fiat_payment_started` — buyer submitted fiat payment proof\n- `awaiting_fiat_received_confirmation` — waiting for partner to confirm fiat receipt\n- `fiat_received_confirmed` — fiat confirmed, crypto release pending\n- `crypto_release_pending` — escrow release in progress\n- `completed` — order fully completed\n- `cancelled` — order cancelled\n- `failed` — terminal error\n- `price_changed_requote_needed` — slippage exceeded at initiation (not a trade status)\n\n**On-ramp statuses (fiat → crypto):**\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_vendor_escrow_funding` — vendor accepted, funding escrow with crypto\n- `awaiting_fiat_transfer` — crypto locked in escrow, partner must confirm payment sent\n- `fiat_transfer_pending` — fiat payment confirmed, awaiting vendor confirmation of receipt\n- `completed` — vendor confirmed fiat received, crypto released to end-user wallet\n- `cancelled` — order cancelled\n- `failed` — escrow error\n\n**On-ramp send-out statuses (optional post-trade cross-chain transfer):**\n\n> After an on-ramp order reaches `completed`, the partner may optionally initiate a\n> **send-out** — a cross-chain transfer of the received crypto to a destination network\n> (e.g. XAI → Arbitrum). This is an additional flow and does not affect the core trade lifecycle.\n> The `completed` status reflects that the trade itself is done; send-out statuses track\n> the subsequent cross-chain transfer only.\n\n- `send_out_pending` — send-out initiated, cross-chain transfer in progress\n- `send_out_completed` — cross-chain transfer successfully delivered to destination\n- `send_out_failed` — cross-chain transfer failed; the on-ramp trade itself remains completed\n","enum":["created","awaiting_liquidity_provider","awaiting_crypto_transfer_authorization","crypto_transfer_authorization_pending","crypto_received","fiat_payment_started","awaiting_fiat_received_confirmation","fiat_received_confirmed","crypto_release_pending","awaiting_vendor_escrow_funding","awaiting_fiat_transfer","fiat_transfer_pending","completed","cancelled","failed","price_changed_requote_needed","send_out_pending","send_out_completed","send_out_failed"]},"PartnerOrderAmounts":{"type":"object","properties":{"crypto_amount":{"type":"string","description":"Total crypto amount (standard units) — the fee-exclusive gross: the buy/sell amount plus the\nplatform fee. Partner-fee-exclusive, except for a crypto-anchored onramp order (originally\nquoted with `crypto_amount`): there the amount was sized up so the delivered amount stays\nexact after the partner fee is withheld, so this field includes the markup. A crypto-anchored\nofframp order carves both fees out of the pinned deposit instead, so this field and\n`fee_breakdown.platform_fee` both shrink relative to a no-markup request, even though the\nmarkup itself stays out of this field. Every other case is unaffected — the markup is\nreported separately in `fee_breakdown.partner_fee`.\n\nOn-ramp — what the end user receives: `crypto_amount − fee_breakdown.platform_fee −\nfee_breakdown.partner_fee` for a fiat-anchored order, or exactly this `crypto_amount` for a\ncrypto-anchored order (both fees are withheld at bridge send-out without changing this\nfield). Off-ramp — what the partner funds at transfer authorization:\n`crypto_amount + fee_breakdown.partner_fee`.\n"},"fiat_amount":{"type":"string","description":"Fiat amount the user receives"},"crypto_currency":{"type":"string","description":"Cryptocurrency code"},"fiat_currency":{"type":"string","description":"Fiat currency code"},"rate":{"type":"string","description":"Crypto-to-fiat exchange rate"}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}},"TimelineEntry":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"timestamp":{"type":"string","format":"date-time","description":"When this status was reached"},"description":{"type":"string","description":"Human-readable description of the status change"}}},"PartnerRecipientContext":{"type":"object","required":["sender_id","recipient_id","recipient_destination_id","sender_recipient_relationship","purpose_of_payment","screening_status"],"properties":{"sender_id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"recipient_destination_id":{"type":"string","format":"uuid"},"sender_recipient_relationship":{"type":"string"},"purpose_of_payment":{"type":"string"},"purpose_details":{"type":"string"},"screening_status":{"type":"string"}}},"PartnerComplianceCase":{"type":"object","required":["case_id","status","risk_decision","requires_review"],"description":"The compliance record for a third-party payout. In v1 the controls run BEFORE the order is\ncreated, so a payout that fails one is rejected with an error rather than returned as a held\norder — see the error codes in the Third-party payouts guide.\n","properties":{"case_id":{"type":"string","format":"uuid","description":"Identifier of the compliance record — the payout's `quote_id`, so a partner and a Unigox\noperator refer to the same record.\n"},"status":{"type":"string","enum":["authorized"],"description":"v1 emits `authorized`; a rejected payout returns an error instead."},"risk_decision":{"type":"string","enum":["allow"],"description":"v1 emits `allow`; a blocking decision surfaces as an error, not a status."},"requires_review":{"type":"boolean","description":"Always false in v1. Operator review happens Unigox-side over the same record."}}}}}}
```

## The PartnerOrderResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerOrderResponse":{"type":"object","properties":{"order_id":{"type":"string","format":"uuid","description":"Unique order identifier"},"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"next_action":{"type":"string","nullable":true,"description":"The action the partner **must** perform to advance the order. `null` means\nno action is required from the partner at this stage — the order is waiting\nfor the counterparty or for an automated system step.\n\nPossible values:\n- `authorize_crypto_transfer` — partner must authorize escrow funding\n- `confirm_fiat_received` — partner must confirm they received the fiat payment\n- `request_new_quote` — price changed, partner must request a new quote\n- `update_kyc_address` — address fields are required for EUR/AUD/GBP payouts; see `action_required` for the endpoint and fields to submit\n- `null` — no required action (waiting for buyer or system)\n","enum":["authorize_crypto_transfer","confirm_fiat_received","request_new_quote","update_kyc_address",null]},"action_required":{"type":"object","nullable":true,"description":"Present when the order needs partner intervention before it can proceed.\nContains machine-readable type, human-readable message, the API endpoint to call,\nand the fields to provide. Null/omitted when no action is needed.\n","required":["type","message"],"properties":{"type":{"type":"string","description":"Machine-readable action identifier"},"message":{"type":"string","description":"Human-readable explanation of what is needed and why"},"endpoint":{"type":"string","description":"API endpoint to call to resolve this action"},"fields":{"type":"array","description":"Fields the partner needs to provide","items":{"type":"object","required":["field","label"],"properties":{"field":{"type":"string","description":"API field name"},"label":{"type":"string","description":"Human-readable description of the field"}}}}}},"allowed_actions":{"type":"array","items":{"type":"string"},"description":"List of API actions the partner **can** perform at the current status. An action\nmay be present here even when `next_action` is `null` — meaning it is optional.\n\nNotable case: `confirm-fiat-received` appears at both `crypto_received` and\n`awaiting_fiat_received_confirmation`. At `crypto_received` it is a **fast-track**:\nif you already know the fiat has arrived via your own systems, you can confirm\nimmediately. Otherwise, wait for the buyer's proof flow to complete — the order\nwill reach `awaiting_fiat_received_confirmation` automatically.\n\nStatus-to-actions mapping:\n- `created` → `[\"cancel\"]`\n- `awaiting_liquidity_provider` → `[\"cancel\"]`\n- `awaiting_crypto_transfer_authorization` → `[\"authorize-crypto-transfer\", \"cancel\"]`\n- `crypto_transfer_authorization_pending` → `[\"cancel\"]`\n- `crypto_received` → `[\"confirm-fiat-received\"]` (optional fast-track)\n- `fiat_payment_started` → `[\"confirm-fiat-received\"]` (optional fast-track after buyer proof submission)\n- `awaiting_fiat_received_confirmation` → `[\"confirm-fiat-received\"]` (required)\n- `fiat_received_confirmed`, `crypto_release_pending`, `completed`, `cancelled`, `failed` → `[]`\n"},"crypto_transfer_authorization_seconds_left":{"type":"integer","format":"int64","nullable":true,"description":"Remaining seconds to authorize crypto transfer while order is in\n`awaiting_crypto_transfer_authorization` / `crypto_transfer_authorization_pending`.\n`null` for all other statuses.\n"},"amounts":{"$ref":"#/components/schemas/PartnerOrderAmounts"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/TimelineEntry"},"description":"Chronological list of status changes"},"has_fiat_settlement_notification":{"type":"boolean","description":"Whether the matched liquidity provider sends fiat settlement notifications.\nWhen `true`, the order will complete automatically when fiat is delivered.\nWhen `false`, you must call `confirm-fiat-received`. You can call\n`confirm-fiat-received` at any time regardless of this value.\n"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"order_type":{"type":"string","nullable":true,"description":"Order type. Present for on-ramp orders. Absent for off-ramp orders.\n- `onramp` — buy crypto with fiat\n"},"vendor_payment_details":{"type":"object","nullable":true,"description":"Vendor's payment details for the end-user to send fiat to.\nPresent only for on-ramp orders at `awaiting_fiat_transfer` status and beyond.\n","properties":{"payment_method_name":{"type":"string"},"payment_network_name":{"type":"string"},"details":{"type":"object","description":"Payment method-specific fields (e.g. account_number, phone)"}}},"payment_request":{"type":"boolean","nullable":true,"description":"`true` if the vendor uses payment request flow — the partner must call\n`submit-payer-details` before `confirm-payment-sent`.\n"},"send_out_tx_hash":{"type":"string","nullable":true,"description":"Destination-chain transaction hash after a send-out completes.\nPresent only for on-ramp orders with `send_out_completed` status.\n"},"recipient_context":{"allOf":[{"$ref":"#/components/schemas/PartnerRecipientContext"}],"nullable":true,"description":"Immutable third-party recipient context; absent for self-payout and on-ramp."},"compliance":{"allOf":[{"$ref":"#/components/schemas/PartnerComplianceCase"}],"nullable":true,"description":"Current durable compliance-case state for a third-party payout."}}},"PartnerOrderStatus":{"type":"string","description":"Order status.\n\n**Off-ramp statuses (crypto → fiat):**\n- `created` — order created, quote locked\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_crypto_transfer_authorization` — partner must authorize crypto transfer via EIP-712 signature\n- `crypto_transfer_authorization_pending` — authorization submitted, awaiting on-chain confirmation\n- `crypto_received` — crypto locked in escrow\n- `fiat_payment_started` — buyer submitted fiat payment proof\n- `awaiting_fiat_received_confirmation` — waiting for partner to confirm fiat receipt\n- `fiat_received_confirmed` — fiat confirmed, crypto release pending\n- `crypto_release_pending` — escrow release in progress\n- `completed` — order fully completed\n- `cancelled` — order cancelled\n- `failed` — terminal error\n- `price_changed_requote_needed` — slippage exceeded at initiation (not a trade status)\n\n**On-ramp statuses (fiat → crypto):**\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_vendor_escrow_funding` — vendor accepted, funding escrow with crypto\n- `awaiting_fiat_transfer` — crypto locked in escrow, partner must confirm payment sent\n- `fiat_transfer_pending` — fiat payment confirmed, awaiting vendor confirmation of receipt\n- `completed` — vendor confirmed fiat received, crypto released to end-user wallet\n- `cancelled` — order cancelled\n- `failed` — escrow error\n\n**On-ramp send-out statuses (optional post-trade cross-chain transfer):**\n\n> After an on-ramp order reaches `completed`, the partner may optionally initiate a\n> **send-out** — a cross-chain transfer of the received crypto to a destination network\n> (e.g. XAI → Arbitrum). This is an additional flow and does not affect the core trade lifecycle.\n> The `completed` status reflects that the trade itself is done; send-out statuses track\n> the subsequent cross-chain transfer only.\n\n- `send_out_pending` — send-out initiated, cross-chain transfer in progress\n- `send_out_completed` — cross-chain transfer successfully delivered to destination\n- `send_out_failed` — cross-chain transfer failed; the on-ramp trade itself remains completed\n","enum":["created","awaiting_liquidity_provider","awaiting_crypto_transfer_authorization","crypto_transfer_authorization_pending","crypto_received","fiat_payment_started","awaiting_fiat_received_confirmation","fiat_received_confirmed","crypto_release_pending","awaiting_vendor_escrow_funding","awaiting_fiat_transfer","fiat_transfer_pending","completed","cancelled","failed","price_changed_requote_needed","send_out_pending","send_out_completed","send_out_failed"]},"PartnerOrderAmounts":{"type":"object","properties":{"crypto_amount":{"type":"string","description":"Total crypto amount (standard units) — the fee-exclusive gross: the buy/sell amount plus the\nplatform fee. Partner-fee-exclusive, except for a crypto-anchored onramp order (originally\nquoted with `crypto_amount`): there the amount was sized up so the delivered amount stays\nexact after the partner fee is withheld, so this field includes the markup. A crypto-anchored\nofframp order carves both fees out of the pinned deposit instead, so this field and\n`fee_breakdown.platform_fee` both shrink relative to a no-markup request, even though the\nmarkup itself stays out of this field. Every other case is unaffected — the markup is\nreported separately in `fee_breakdown.partner_fee`.\n\nOn-ramp — what the end user receives: `crypto_amount − fee_breakdown.platform_fee −\nfee_breakdown.partner_fee` for a fiat-anchored order, or exactly this `crypto_amount` for a\ncrypto-anchored order (both fees are withheld at bridge send-out without changing this\nfield). Off-ramp — what the partner funds at transfer authorization:\n`crypto_amount + fee_breakdown.partner_fee`.\n"},"fiat_amount":{"type":"string","description":"Fiat amount the user receives"},"crypto_currency":{"type":"string","description":"Cryptocurrency code"},"fiat_currency":{"type":"string","description":"Fiat currency code"},"rate":{"type":"string","description":"Crypto-to-fiat exchange rate"}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}},"TimelineEntry":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"timestamp":{"type":"string","format":"date-time","description":"When this status was reached"},"description":{"type":"string","description":"Human-readable description of the status change"}}},"PartnerRecipientContext":{"type":"object","required":["sender_id","recipient_id","recipient_destination_id","sender_recipient_relationship","purpose_of_payment","screening_status"],"properties":{"sender_id":{"type":"string","format":"uuid"},"recipient_id":{"type":"string","format":"uuid"},"recipient_destination_id":{"type":"string","format":"uuid"},"sender_recipient_relationship":{"type":"string"},"purpose_of_payment":{"type":"string"},"purpose_details":{"type":"string"},"screening_status":{"type":"string"}}},"PartnerComplianceCase":{"type":"object","required":["case_id","status","risk_decision","requires_review"],"description":"The compliance record for a third-party payout. In v1 the controls run BEFORE the order is\ncreated, so a payout that fails one is rejected with an error rather than returned as a held\norder — see the error codes in the Third-party payouts guide.\n","properties":{"case_id":{"type":"string","format":"uuid","description":"Identifier of the compliance record — the payout's `quote_id`, so a partner and a Unigox\noperator refer to the same record.\n"},"status":{"type":"string","enum":["authorized"],"description":"v1 emits `authorized`; a rejected payout returns an error instead."},"risk_decision":{"type":"string","enum":["allow"],"description":"v1 emits `allow`; a blocking decision surfaces as an error, not a status."},"requires_review":{"type":"boolean","description":"Always false in v1. Operator review happens Unigox-side over the same record."}}}}}}
```

## The PartnerOrderAmounts object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerOrderAmounts":{"type":"object","properties":{"crypto_amount":{"type":"string","description":"Total crypto amount (standard units) — the fee-exclusive gross: the buy/sell amount plus the\nplatform fee. Partner-fee-exclusive, except for a crypto-anchored onramp order (originally\nquoted with `crypto_amount`): there the amount was sized up so the delivered amount stays\nexact after the partner fee is withheld, so this field includes the markup. A crypto-anchored\nofframp order carves both fees out of the pinned deposit instead, so this field and\n`fee_breakdown.platform_fee` both shrink relative to a no-markup request, even though the\nmarkup itself stays out of this field. Every other case is unaffected — the markup is\nreported separately in `fee_breakdown.partner_fee`.\n\nOn-ramp — what the end user receives: `crypto_amount − fee_breakdown.platform_fee −\nfee_breakdown.partner_fee` for a fiat-anchored order, or exactly this `crypto_amount` for a\ncrypto-anchored order (both fees are withheld at bridge send-out without changing this\nfield). Off-ramp — what the partner funds at transfer authorization:\n`crypto_amount + fee_breakdown.partner_fee`.\n"},"fiat_amount":{"type":"string","description":"Fiat amount the user receives"},"crypto_currency":{"type":"string","description":"Cryptocurrency code"},"fiat_currency":{"type":"string","description":"Fiat currency code"},"rate":{"type":"string","description":"Crypto-to-fiat exchange rate"}}}}}}
```

## The FeeBreakdown object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}}}}}
```

## The PartnerOrderStatus object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerOrderStatus":{"type":"string","description":"Order status.\n\n**Off-ramp statuses (crypto → fiat):**\n- `created` — order created, quote locked\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_crypto_transfer_authorization` — partner must authorize crypto transfer via EIP-712 signature\n- `crypto_transfer_authorization_pending` — authorization submitted, awaiting on-chain confirmation\n- `crypto_received` — crypto locked in escrow\n- `fiat_payment_started` — buyer submitted fiat payment proof\n- `awaiting_fiat_received_confirmation` — waiting for partner to confirm fiat receipt\n- `fiat_received_confirmed` — fiat confirmed, crypto release pending\n- `crypto_release_pending` — escrow release in progress\n- `completed` — order fully completed\n- `cancelled` — order cancelled\n- `failed` — terminal error\n- `price_changed_requote_needed` — slippage exceeded at initiation (not a trade status)\n\n**On-ramp statuses (fiat → crypto):**\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_vendor_escrow_funding` — vendor accepted, funding escrow with crypto\n- `awaiting_fiat_transfer` — crypto locked in escrow, partner must confirm payment sent\n- `fiat_transfer_pending` — fiat payment confirmed, awaiting vendor confirmation of receipt\n- `completed` — vendor confirmed fiat received, crypto released to end-user wallet\n- `cancelled` — order cancelled\n- `failed` — escrow error\n\n**On-ramp send-out statuses (optional post-trade cross-chain transfer):**\n\n> After an on-ramp order reaches `completed`, the partner may optionally initiate a\n> **send-out** — a cross-chain transfer of the received crypto to a destination network\n> (e.g. XAI → Arbitrum). This is an additional flow and does not affect the core trade lifecycle.\n> The `completed` status reflects that the trade itself is done; send-out statuses track\n> the subsequent cross-chain transfer only.\n\n- `send_out_pending` — send-out initiated, cross-chain transfer in progress\n- `send_out_completed` — cross-chain transfer successfully delivered to destination\n- `send_out_failed` — cross-chain transfer failed; the on-ramp trade itself remains completed\n","enum":["created","awaiting_liquidity_provider","awaiting_crypto_transfer_authorization","crypto_transfer_authorization_pending","crypto_received","fiat_payment_started","awaiting_fiat_received_confirmation","fiat_received_confirmed","crypto_release_pending","awaiting_vendor_escrow_funding","awaiting_fiat_transfer","fiat_transfer_pending","completed","cancelled","failed","price_changed_requote_needed","send_out_pending","send_out_completed","send_out_failed"]}}}}
```

## The TimelineEntry object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"TimelineEntry":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"timestamp":{"type":"string","format":"date-time","description":"When this status was reached"},"description":{"type":"string","description":"Human-readable description of the status change"}}},"PartnerOrderStatus":{"type":"string","description":"Order status.\n\n**Off-ramp statuses (crypto → fiat):**\n- `created` — order created, quote locked\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_crypto_transfer_authorization` — partner must authorize crypto transfer via EIP-712 signature\n- `crypto_transfer_authorization_pending` — authorization submitted, awaiting on-chain confirmation\n- `crypto_received` — crypto locked in escrow\n- `fiat_payment_started` — buyer submitted fiat payment proof\n- `awaiting_fiat_received_confirmation` — waiting for partner to confirm fiat receipt\n- `fiat_received_confirmed` — fiat confirmed, crypto release pending\n- `crypto_release_pending` — escrow release in progress\n- `completed` — order fully completed\n- `cancelled` — order cancelled\n- `failed` — terminal error\n- `price_changed_requote_needed` — slippage exceeded at initiation (not a trade status)\n\n**On-ramp statuses (fiat → crypto):**\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_vendor_escrow_funding` — vendor accepted, funding escrow with crypto\n- `awaiting_fiat_transfer` — crypto locked in escrow, partner must confirm payment sent\n- `fiat_transfer_pending` — fiat payment confirmed, awaiting vendor confirmation of receipt\n- `completed` — vendor confirmed fiat received, crypto released to end-user wallet\n- `cancelled` — order cancelled\n- `failed` — escrow error\n\n**On-ramp send-out statuses (optional post-trade cross-chain transfer):**\n\n> After an on-ramp order reaches `completed`, the partner may optionally initiate a\n> **send-out** — a cross-chain transfer of the received crypto to a destination network\n> (e.g. XAI → Arbitrum). This is an additional flow and does not affect the core trade lifecycle.\n> The `completed` status reflects that the trade itself is done; send-out statuses track\n> the subsequent cross-chain transfer only.\n\n- `send_out_pending` — send-out initiated, cross-chain transfer in progress\n- `send_out_completed` — cross-chain transfer successfully delivered to destination\n- `send_out_failed` — cross-chain transfer failed; the on-ramp trade itself remains completed\n","enum":["created","awaiting_liquidity_provider","awaiting_crypto_transfer_authorization","crypto_transfer_authorization_pending","crypto_received","fiat_payment_started","awaiting_fiat_received_confirmation","fiat_received_confirmed","crypto_release_pending","awaiting_vendor_escrow_funding","awaiting_fiat_transfer","fiat_transfer_pending","completed","cancelled","failed","price_changed_requote_needed","send_out_pending","send_out_completed","send_out_failed"]}}}}
```

## The RegisterWebhookRequest object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"RegisterWebhookRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"HTTPS URL that will receive webhook events"}}}}}}
```

## The WebhookConfigResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"WebhookConfigResponse":{"type":"object","properties":{"url":{"type":"string","nullable":true,"description":"Currently configured webhook URL (null if not set)"},"enabled":{"type":"boolean","description":"Whether webhook delivery is active"}}}}}}
```

## The WebhookTestPingResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"WebhookTestPingResponse":{"type":"object","properties":{"delivered":{"type":"boolean","description":"Whether the test event was delivered successfully"},"http_status":{"type":"integer","description":"HTTP status code returned by the webhook endpoint"}}}}}}
```

## The WebhookEventPayload object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"WebhookEventPayload":{"type":"object","description":"Payload shape delivered to your webhook URL on each order status change","properties":{"event_id":{"type":"string","description":"Unique event identifier for de-duplication"},"event_type":{"type":"string","description":"Event type (always `order.status.changed`)"},"created_at":{"type":"string","format":"date-time","description":"Timestamp when the event was created"},"data":{"type":"object","properties":{"order_id":{"type":"string","format":"uuid","description":"Partner order UUID"},"status":{"$ref":"#/components/schemas/PartnerOrderStatus"},"user_id":{"type":"string","format":"uuid","description":"Public UUID of the end-user (from user creation)"},"crypto_amount":{"type":"string","description":"Total crypto amount including the platform fee. Partner-fee-exclusive, except for a\ncrypto-anchored onramp order: there the amount was sized up so the delivered amount\nstays exact after the partner fee is withheld, so this field includes the markup.\nEvery other case keeps the markup out — it's reported separately in `partner_fee`.\nFormatted with the shortest round-tripping representation (e.g. `\"101.5\"`, not\n`\"101.500000\"`) — parse it as a decimal, don't compare strings.\n"},"crypto_currency":{"type":"string","description":"Cryptocurrency code"},"partner_fee":{"type":"string","description":"Partner markup in crypto for this order (`\"0\"` when none was declared). Unlike\n`fee_breakdown.partner_fee` in the REST responses (which is padded to the token's\ndecimals), this field is formatted with the shortest round-tripping representation —\n`1.0` reports as `\"1\"`, not `\"1.00\"`. Parse it as a decimal, don't compare strings.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Partner markup percentage declared for this order (`1` means 1%; 0 when none)"},"fiat_amount":{"type":"string","description":"Fiat amount"},"fiat_currency":{"type":"string","description":"Fiat currency code"},"provider":{"type":"string","description":"Liquidity provider type","enum":["p2p","licensed"]},"payment_details_id":{"type":"string","description":"Payment details ID used for the order"}}}}},"PartnerOrderStatus":{"type":"string","description":"Order status.\n\n**Off-ramp statuses (crypto → fiat):**\n- `created` — order created, quote locked\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_crypto_transfer_authorization` — partner must authorize crypto transfer via EIP-712 signature\n- `crypto_transfer_authorization_pending` — authorization submitted, awaiting on-chain confirmation\n- `crypto_received` — crypto locked in escrow\n- `fiat_payment_started` — buyer submitted fiat payment proof\n- `awaiting_fiat_received_confirmation` — waiting for partner to confirm fiat receipt\n- `fiat_received_confirmed` — fiat confirmed, crypto release pending\n- `crypto_release_pending` — escrow release in progress\n- `completed` — order fully completed\n- `cancelled` — order cancelled\n- `failed` — terminal error\n- `price_changed_requote_needed` — slippage exceeded at initiation (not a trade status)\n\n**On-ramp statuses (fiat → crypto):**\n- `awaiting_liquidity_provider` — order initiated, waiting for vendor acceptance\n- `awaiting_vendor_escrow_funding` — vendor accepted, funding escrow with crypto\n- `awaiting_fiat_transfer` — crypto locked in escrow, partner must confirm payment sent\n- `fiat_transfer_pending` — fiat payment confirmed, awaiting vendor confirmation of receipt\n- `completed` — vendor confirmed fiat received, crypto released to end-user wallet\n- `cancelled` — order cancelled\n- `failed` — escrow error\n\n**On-ramp send-out statuses (optional post-trade cross-chain transfer):**\n\n> After an on-ramp order reaches `completed`, the partner may optionally initiate a\n> **send-out** — a cross-chain transfer of the received crypto to a destination network\n> (e.g. XAI → Arbitrum). This is an additional flow and does not affect the core trade lifecycle.\n> The `completed` status reflects that the trade itself is done; send-out statuses track\n> the subsequent cross-chain transfer only.\n\n- `send_out_pending` — send-out initiated, cross-chain transfer in progress\n- `send_out_completed` — cross-chain transfer successfully delivered to destination\n- `send_out_failed` — cross-chain transfer failed; the on-ramp trade itself remains completed\n","enum":["created","awaiting_liquidity_provider","awaiting_crypto_transfer_authorization","crypto_transfer_authorization_pending","crypto_received","fiat_payment_started","awaiting_fiat_received_confirmation","fiat_received_confirmed","crypto_release_pending","awaiting_vendor_escrow_funding","awaiting_fiat_transfer","fiat_transfer_pending","completed","cancelled","failed","price_changed_requote_needed","send_out_pending","send_out_completed","send_out_failed"]}}}}
```

## The PartnerOnRampQuoteRequest object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerOnRampQuoteRequest":{"type":"object","required":["user_uuid","fiat_currency","crypto_currency"],"oneOf":[{"required":["fiat_amount"]},{"required":["crypto_amount"]}],"properties":{"user_uuid":{"type":"string","format":"uuid","description":"Public UUID of the end-user (returned by `POST /partner/users` as `id`)"},"fiat_amount":{"type":"string","description":"Fiat amount the end-user will send. Provide exactly one of fiat_amount or crypto_amount."},"crypto_amount":{"type":"string","description":"Crypto amount to receive. Provide exactly one of fiat_amount or crypto_amount."},"fiat_currency":{"type":"string","description":"Source fiat currency code (ISO 4217)"},"crypto_currency":{"type":"string","description":"Target cryptocurrency code (e.g., USDT, USDC)"},"payment_method_slug":{"type":"string","description":"Optional payment method slug to narrow routing"},"payment_network_slug":{"type":"string","description":"Optional payment network slug to narrow routing"},"provider_scope":{"type":"string","description":"Optional provider scope filter; defaults to all","enum":["all","licensed_only","p2p_only"]},"country_code":{"type":"string","description":"Optional ISO 2-letter country code for fee/routing context"},"partner_fee_pct":{"type":"string","pattern":"^[0-9]+(\\.[0-9]+)?$","description":"Optional per-order markup you charge your end user, applied on the same crypto base\nas the platform fee (non-compounding). Percent units: `1` means 1%. Omitting the field\nfrom this request payload — or sending `0` — means no markup; the markup cannot be\nnegative. Fiat-anchored offramp orders have no cap — the deposit grows to cover the\nmarkup. Every other case hits a formula domain bound, not a business cap: onramp\nrejects `partner_fee_pct >= 100` (the withheld or grossed-up fee would consume the\nentire buyer amount, leaving nothing delivered); crypto-anchored offramp rejects it\nwhen the fee would consume the entire pinned deposit.\n\nNote: you send this value as a JSON **string**, but it round-trips as a JSON **number**\nin `fee_breakdown.partner_fee_pct` and in the webhook `data.partner_fee_pct` field —\ndon't parse those as strings.\n\n**Rejection reference:**\n\n| Rejection | `error.code` | HTTP status |\n|---|---|---|\n| `partner_fee_pct` is non-numeric, negative, `NaN`, or `Inf` | `INVALID_REQUEST` | 400 |\n| Onramp: fee would consume the entire buyer amount (`partner_fee_pct >= 100`) | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: fee would consume the entire pinned deposit | `INVALID_REQUEST` | 400 |\n| Fiat-anchored offramp: the computed fee is not a finite amount | `INVALID_REQUEST` | 400 |\n| Crypto-anchored offramp: the carved fee-exclusive gross falls outside the matched offer's min/max | `NO_OFFERS_AVAILABLE` | 409 |\n","default":"0"}}}}}}
```

## The PartnerOnRampQuoteResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerOnRampQuoteResponse":{"type":"object","properties":{"quote_id":{"type":"string","format":"uuid","description":"Unique quote identifier — use this to initiate the order"},"rate":{"type":"string","description":"Fiat-to-crypto exchange rate (fiat units per 1 crypto unit)"},"crypto_amount":{"type":"string","description":"Total crypto amount for the order — the end-user's amount plus the platform fee.\nPartner-fee-exclusive on a fiat-anchored quote (request declared `fiat_amount`): the markup\nis reported separately in `fee_breakdown.partner_fee` and is not added here — the end user\nreceives `crypto_amount − fee_breakdown.platform_fee − fee_breakdown.partner_fee`, withheld\nfrom this amount at bridge send-out. On a crypto-anchored quote (request declared\n`crypto_amount`), the trade is sized up so this field includes the markup, and the end user\nreceives exactly the requested `crypto_amount`, unchanged by the markup.\n"},"fiat_amount":{"type":"string","description":"Fiat amount the end-user sends"},"fee_breakdown":{"$ref":"#/components/schemas/FeeBreakdown"},"payment_method_slug":{"type":"string","description":"Matched payment method slug used for this quote"},"payment_network_slug":{"type":"string","description":"Matched payment network slug used for this quote"},"expires_at":{"type":"string","format":"date-time","description":"Quote expiration timestamp (60 seconds from creation)"}}},"FeeBreakdown":{"type":"object","description":"Transparent breakdown of all fee components (amounts in crypto)","properties":{"platform_fee":{"type":"string","description":"Platform fee in crypto"},"platform_fee_pct":{"type":"number","format":"double","description":"Platform fee as a percentage"},"partner_fee":{"type":"string","description":"Your markup in crypto, computed from the `partner_fee_pct` you declared on the\nquote/estimate request, on the same base as the platform fee. `0` when no markup was declared.\n"},"partner_fee_pct":{"type":"number","format":"double","description":"Your markup percentage as declared on the request (`1` means 1%); `0` when none."},"total_fee":{"type":"string","description":"Total fee in crypto (platform_fee + partner_fee)"}}}}}}
```

## The SubmitPayerDetailsRequest object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"SubmitPayerDetailsRequest":{"type":"object","description":"Submit the payer's payment details for a `payment_request` on-ramp order.\nProvide exactly one of `payment_details_id` (saved profile) or `payment_details` (inline object).\n","oneOf":[{"required":["payment_details_id"],"properties":{"payment_details_id":{"type":"integer","format":"int64","description":"ID of a previously saved payment profile for the end-user"}}},{"required":["payment_details"],"properties":{"payment_details":{"type":"object","required":["payment_method_id","payment_network_id","details"],"properties":{"payment_method_id":{"type":"integer","format":"int64","description":"Payment method ID"},"payment_network_id":{"type":"integer","format":"int64","description":"Payment network ID"},"details":{"type":"object","description":"Payer's account details (fields depend on payment method)","additionalProperties":true},"country_code":{"type":"string","description":"Optional ISO 2-letter country code"}}}}}]}}}}
```

## The PartnerErrorResponse object

```json
{"openapi":"3.0.3","info":{"title":"Unigox API Gateway","version":"1.0.0"},"components":{"schemas":{"PartnerErrorResponse":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Possible values:\n- `QUOTE_EXPIRED` — quote TTL has passed\n- `QUOTE_NOT_FOUND` — quote ID does not exist\n- `QUOTE_ALREADY_USED` — quote was already consumed by initiate\n- `PRICE_CHANGED_REQUOTE` — price changed beyond slippage tolerance, partner should request a new quote\n- `ORDER_NOT_FOUND` — order ID does not exist or belongs to another partner\n- `NO_OFFERS_AVAILABLE` — no vendor offers for the requested pair\n- `INVALID_REQUEST` — malformed request or missing fields\n- `INVALID_PAYMENT_DETAILS` — payment details not found or invalid\n- `RAIL_ROUTE_MISMATCH` — rail or route does not match payment details\n- `UNAUTHORIZED` — authentication failed\n- `INTERNAL_ERROR` — unexpected server error\n- `INVALID_STATUS` — order is not in the correct status for the requested action\n- `TRANSACTOR_ERROR` — on-chain transaction relay failed (e.g. simulation reverted)\n- `INSUFFICIENT_BALANCE` — partner wallet lacks tokens to fund escrow\n- `OPERATION_NOT_ALLOWED` — action is not permitted for the current order state\n- `KYC_NOT_CLEARED` — the customer is not KYC-cleared for this transaction; `error.details.kyc_status` carries their current partner-facing status\n- `SENDER_IDENTITY_REQUIRED` — the payout corridor settles consumer-to-consumer and the customer's record cannot name them as the remitter; `error.details.kyc_fields` lists what to send to `PATCH /partner/users/{user_uuid}/kyc`\n","enum":["QUOTE_EXPIRED","QUOTE_NOT_FOUND","QUOTE_ALREADY_USED","PRICE_CHANGED_REQUOTE","ORDER_NOT_FOUND","NO_OFFERS_AVAILABLE","INVALID_REQUEST","INVALID_PAYMENT_DETAILS","RAIL_ROUTE_MISMATCH","UNAUTHORIZED","INTERNAL_ERROR","INVALID_STATUS","TRANSACTOR_ERROR","INSUFFICIENT_BALANCE","OPERATION_NOT_ALLOWED","KYC_NOT_CLEARED","SENDER_IDENTITY_REQUIRED"]},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","nullable":true,"additionalProperties":true,"description":"Additional context (e.g., quoted_rate, current_rate for slippage errors)"}}}}}}}}
```


# Changelog

Notable changes to the Unigox partner API, newest first.

## 2026-08-11

**Chinese mobile wallets pay Chinese nationals only, and WeChat pays fewer of them than Alipay.** Both wallets verify the account holder against a Chinese national ID, so a foreign resident's Alipay or WeChat account cannot receive on the `ewallet` format — use a bank format for those beneficiaries.

* `alipay` settles to the sender themselves, to family, or to a third party.
* `wechat-pay` settles to the sender themselves or to family only. A `wechat-pay` destination quoted as `supplier`, `employee` or `friend` is refused before any money moves.
* The receiving wallet may ask the beneficiary to evidence the declared relationship (proof of income for a payment to the sender themselves, proof of the relationship for family). A relationship the beneficiary cannot evidence is what leaves a payment held inside the wallet.
* Paying a Chinese company from your company (B2C) is not available on this rail yet.

Nothing changes for the bank formats on `cnaps`, which carry no such restriction.

## 2026-08-07

**Consumer-to-consumer payout rails now require the sender's own identity.** Chinese mobile wallets (Alipay, WeChat Pay — the `ewallet` format on `cnaps`) settle person to person: the remitter shown on the receiving wallet must be your paying customer, not Unigox and not your company. When the customer's KYC record cannot name them, `POST /api/v1/partner/offramp/initiate` returns `422 SENDER_IDENTITY_REQUIRED`. No order is created and the quote is reverted.

* `error.details.missing_fields` is what the rail needs; `error.details.kyc_fields` is the subset you can supply, as the request body of `PATCH /api/v1/partner/users/{user_uuid}/kyc`. Send them, then retry with a fresh quote.
* That endpoint gains four fields for this: `gender` (`M`/`F`), `nationality` and `id_issue_country` (ISO 3166-1 alpha-2), and `source_of_funds` (a fixed vocabulary — see the endpoint). `id_type` also accepts `RESIDENCE_PERMIT`. An unacceptable value is a `400` with `invalid_gender`, `invalid_country_code`, `invalid_source_of_funds` or `invalid_field_value` (a value sent as an object or an array), and nothing is written — the patch is all-or-nothing.
* A recognised field sent blank is a no-op, not an error: the response is `200` and the field is absent from `updated_fields`, so a patch whose recognised fields are all blank answers `{"updated_fields": []}` and the payout still refuses. Read `updated_fields` before retrying.
* 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.

Nothing changes for bank-format payouts, on-ramp, or any corridor other than the wallet formats. Send the customer's own details: substituting anyone else's is what the rail's identity check exists to catch.

## 2026-07-29

**Third-party recipients are now first-class Partner API resources.** A KYC-verified sender can pay a separately registered recipient without representing that recipient as one of the sender's own payment methods.

* Register and manage recipients under `/api/v1/partner/recipients`, including a versioned payout destination.
* Bind `sender_recipient_relationship` and `purpose_of_payment` for each payment.
* Quote an off-ramp with `sender_id`, `recipient_id`, and `recipient_destination_id`; omit `payment_details_id` for this ownership mode.
* Quote and order responses expose the immutable `recipient_context`. Initiation creates a durable `compliance` case visible in the same Unigox Compliance queue used for portal-created payouts.
* Sensitive identity and account values are write-only or masked on reads. Vendor-specific beneficiary terminology is not exposed by the Partner API.

The KYC user identified by `sender_id` must be the real party funding the payout. Partners must not route multiple real customers through a shared shell sender.

## 2026-07-22

**New KYC status `UNDER_REVIEW` for customers under manual compliance review.** When a customer's screening needs human compliance review (rather than a quick automated check), their KYC status is now surfaced as `UNDER_REVIEW` instead of `IN_PROGRESS`. It appears wherever a KYC status is reported: `GET /partner/users/{user_uuid}/verification-status`, `GET /partner/users/{user_uuid}` (`kyc.status`), the KYC submit response (`kyc_status`), and the `user.kyc.updated` webhook. A manual review can take **up to 24 hours**, unlike an automated check of a couple of minutes — so you can now tell the two apart and set the right expectation with your customer.

`UNDER_REVIEW` is **not cleared**: treat it exactly like `IN_PROGRESS` for gating — keep polling and do not unlock onramp/offramp until the customer reaches `VERIFIED`. This is an additive status value; if you already wait for `VERIFIED` before transacting, no action is needed.

## 2026-07-21

**On-ramp and off-ramp price estimates are public.** `POST /api/v1/partner/onramp/estimate` and `POST /api/v1/partner/offramp/estimate` no longer require an API key — same as `GET /api/v1/partner/liquidity`. They return indicative pricing only (no liquidity reservation, no quote or order). Executable pricing and order creation still need a key: `POST /partner/onramp/quote`, `/onramp/initiate`, `/offramp/quote`, and `/offramp/initiate`.

No code changes required if you already call estimate with a key; the key is simply optional for these two endpoints.

## 2026-07-13

**You can now charge your end users a per-order markup.** Pass an optional `partner_fee_pct` on the quote and estimate requests for both ramps (`POST /onramp/estimate`, `/onramp/quote`, `/offramp/estimate`, `/offramp/quote`) — `1` means 1%. The markup is declared per order (no stored config), computed in crypto on the same base as the platform fee. For off-ramp, the markup is settled on-chain to your partner wallet when the order releases successfully; cancelled or failed orders refund the full amount, markup included. For on-ramp there's no separate transfer leg: the full buyer amount lands in your partner wallet at release, and the markup is captured by withholding it when the crypto bridges out at send-out (a crypto-anchored order is sized up beforehand so the requested amount still arrives net of the withheld fee).

* Quote, estimate, initiate, and GET/LIST order responses report the real figures in `fee_breakdown.partner_fee` and `fee_breakdown.partner_fee_pct`, and webhook event `data` carries `partner_fee` / `partner_fee_pct` too. The compact action responses (`confirm-payment-sent`, `confirm-fiat-received`, `cancel`, `authorize-crypto-transfer`) don't carry `fee_breakdown` — re-fetch `GET /orders/{order_id}` for the fee figures after those calls.
* `crypto_amount` stays partner-fee-exclusive in composition, with two exceptions to what it's exclusive of: 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 move 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 offramp funding amount from transfer authorization, which is `crypto_amount + partner_fee`). Apply your fee on the **same side** the platform does, or your receipts won't reconcile with our orders.
* Fiat-anchored offramp orders have no cap — the deposit grows to cover the markup. 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.

Pricing is unchanged if you don't charge a markup: omit `partner_fee_pct` (or send `0`) and every amount is what it was — only `partner_fee` and `partner_fee_pct` read zero. The platform fee still applies, so `platform_fee` and `total_fee` keep reporting it (with `total_fee` equal to `platform_fee`).

One formatting change does reach every partner, including those not charging a markup: in REST responses, `fee_breakdown.partner_fee` previously always returned the literal `"0.00"` and now carries the token's own decimals, like the other fee fields — a zero-fee USDT order reports `"0.000000"`. The value is unchanged; parse it as a decimal rather than comparing strings. This does not apply to the webhook's `data.partner_fee` field, which always used (and still uses) the shortest round-tripping representation — `"1"`, not `"1.00"`; `"0"` for no fee.

## 2026-06-27

**Customers must be KYC-cleared before onramping or offramping.** Quote and initiate calls (`POST /onramp/quote`, `/onramp/initiate`, `/offramp/quote`, `/offramp/initiate`) return `422 KYC_NOT_CLEARED` if the customer is not cleared; `error.details.kyc_status` carries their current status. Poll `GET /partner/users/{user_uuid}/verification-status` until the customer reaches `VERIFIED`.

No action needed if you already wait for `VERIFIED` before transacting.

## 2026-06-24

**Recipient validation moved to recipient creation.** The recipient field check that previously ran when you created an offramp order now runs earlier — when you **create the recipient** (`POST /api/v1/partner/users/{user_uuid}/payment-details`):

* If a required recipient field is missing or malformed for the payout corridor, the create is rejected with **`422 Unprocessable Entity`** and the offending fields are named in the `error` message — so you fix the recipient once, up front, before it can be used in any order.
* `POST /api/v1/partner/offramp/initiate` **no longer returns `RECIPIENT_UNVERIFIABLE`**: a recipient you successfully created has already passed field validation. Quote and funds checks at initiate are unchanged.
* This is now the single point of recipient validation — there is no second check at order time. It confirms the recipient details are complete and well-formed for the corridor; it does not by itself guarantee the destination account exists. If the validation service is temporarily unavailable the recipient is still created (creation is never blocked by an outage), so create recipients before you need them.

No action needed if you already create recipients before ordering. Corridors without recipient requirements are unchanged.

## 2026-06-22

**Offramp orders now validate the recipient details before the order is created.**

For payout corridors with recipient requirements (e.g. mobile money and bank transfers in Nigeria, Ghana, and Kenya), `POST /api/v1/partner/offramp/initiate` now checks the recipient up front:

* If a required recipient field is missing or malformed, the request is rejected with **`422 RECIPIENT_UNVERIFIABLE`** and the offending fields are listed in `error.details.fields` — so you can fix the details and create a new order, instead of creating one that can't be paid out.
* Name requirements are corridor-specific: bank transfers expect the account-holder name, while mobile money pays to the number and accepts a placeholder name.
* This confirms the recipient details are complete and well-formed; it does not by itself guarantee the destination account exists. If the up-front check can't be completed, the order is still created and the recipient is re-checked before payout — so an outage never blocks order creation.

Corridors without recipient requirements are unchanged; no action required.


