API Overview
Uapi provides a high-performance, non-custodial crypto payment infrastructure for global merchants. Our API makes complex blockchain interactions as simple as traditional payments.
Base URL
https://uapi.io/api/v1
1 Authentication
All requests must include your API Key in HTTP headers. Keys are split into Test Key (sandbox) and Live Key (production).
X-API-KEY: sk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Every request goes through these 6 checks (in order)
| # | Check | On failure | How to fix |
|---|---|---|---|
| 1 | X-API-KEY header present | 401 Missing API Key | Add the X-API-KEY header |
| 2 | Caller IP not on system blocklist | 403 IP Blocked | Auto-blocked after repeated failed attempts; contact support |
| 3 | API Key valid and account active | 403 Invalid API Key / Account suspended | Copy the current key from API Settings; suspended accounts need support |
| 4 | Plan not expired (paid plans) | 403 Plan expired | Renew or downgrade to Free |
| 5 | Caller IP in your API IP whitelist (if enabled) | 403 IP not in whitelist | Add the caller IP under API Security; or disable the whitelist |
| 6 | Request origin domain bound to this account | 403 Access Denied: ... | Two options: browser-side requests must send Origin/Referer; server-to-server calls must include domain (a bound website) in the JSON body |
Extra "chain" permission check (only on /order/create.php)
When you pass the chain field, the API also checks whether that chain is enabled on your current plan. Free typically allows TRC20 only — upgrading to Pro unlocks more chains. Failure returns 403 Access Denied: Chain '…' is not enabled for your current plan. See Subscription / Plan in the console for your currently enabled chains.
Domain binding quick reference
Browser-side: the browser sends Origin or Referer; UAPI matches that to a bound domain.
Server-side: curl / backend SDK calls do not send those headers, so include "domain": "yoursite.com" in the JSON body.
Bind sites under Merchant Console → API Settings → Website Bindings.
Integration Flow
Create Order
Merchant calls the API with amount and network, then receives a payment link (payment_url).
User Pays
Redirect users to checkout, where they complete payment via QR scan or transfer.
Confirm & Callback
After on-chain confirmation, the system sends a webhook and updates order status automatically.
No-Code Integration
If you do not want to write code, you can use our Payment Link feature:
- Generate a dedicated payment page in one click from the merchant console.
- Set fixed amounts or allow customers to enter custom amounts.
- Share the link directly through social media, Telegram, or WhatsApp.
- After successful payment, settlement details remain available in your dashboard.
Create Order
POSTCreate a brand-new payment order and receive a checkout link.
Endpoint
/order/create.php
| Parameter | Type | Required | Description |
|---|---|---|---|
| amount | Float | YES | Order amount (USDT) |
| chain | String | YES | Network: trc20, erc20, bsc, solana, polygon, etc. |
| merchant_order_id | String | YES | Your merchant order number (unique) |
| notify_url | String | NO | Webhook URL. A POST callback will be sent after successful payment. |
| domain | String | YES* | Required for server-to-server requests (e.g., curl/backend SDK). Use your bound website domain, e.g. yoursite.com. It can be omitted if Origin/Referer is present. |
| currency | String | NO | Settlement token: USDT (default) or USDC. Note: the backend accepts currency only, not token. |
{
"status": "success",
"data": {
"order_no": "PAY2024...", // UAPI-generated order number (used by every subsequent call) "amount": 100.001234, // Order amount (echoed back) "currency": "USDT", // Settlement token "chain": "trc20", // Settlement chain "expire_in": 600, // Order TTL in seconds (relative) "payment_url": "https://...", // Checkout URL — redirect the buyer here "fast_sync_enabled": true // Whether fast on-chain confirmation is enabled for this order }
}
⚠️ Older docs referenced order_id / token / payment_address / qr_code / expire_at (timestamp). Those fields are NOT returned — integrate against the keys above.
Check Order Status
GETpay_access_token returned by create (already embedded in payment_url) — designed mainly for the checkout page to poll. Server integrators typically receive payment success via the Webhook callback rather than polling. Endpoint
GET /api/v1/order/status.php?order_no=PAY...&token=ACCESS_TOKEN
Query Parameters
| Param | Required | Meaning |
|---|---|---|
| order_no | YES | UAPI order number from create response |
| token | YES | Per-order access token; extract it from the payment_url query string returned at create time |
Responses (by state)
// Paid{ "status": "paid", "tx_hash": "0x123abc..." }
// Awaiting payment{ "status": "pending" }
// Order expired{ "status": "expired" }
// Error{ "status": "error", "error": "Invalid token" }
Rate limits: 30 / min per IP, 40 / 10s per order across all IPs. Server integrators should rely on Webhook callbacks instead of polling.
Submit Dispute
POSTWhen a buyer underpaid or paid in the wrong currency, the merchant can flag the order as disputed and optionally rewrite the recorded amount / currency to match what was actually received.
Endpoint
POST /api/v1/order/dispute.php
Headers
X-API-KEY: sk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Body
| Field | Required | Meaning |
|---|---|---|
| merchant_order_id | YES* | Your merchant order ID. Either this or order_no must be provided. |
| order_no | YES* | UAPI order number |
| domain | YES* | Required for server-side calls; must be a bound domain (same rule as create.php) |
| mode | NO | original (default, keep original amount) or adjusted (rewrite using new_amount / new_currency) |
| new_amount | NO | Used when mode=adjusted; cannot exceed the original amount (clamped if it does) |
| new_currency | NO | Used when mode=adjusted; USDT or USDC |
| note | NO | Free-form note appended to the order's notes column |
Success Response
{
"status": "success",
"data": {
"order_id": 12345,
"order_no": "UAPI20260408001",
"merchant_order_id": "ORD-001",
"order_status": "disputed",
"amount": "100.000000",
"currency": "USDT",
"mode": "original"
}
}
Rate limit: 20 / min per IP. After submission the order status flips to disputed — no further Webhook will fire.
Webhook Callback
Once the on-chain transaction is confirmed, UAPI immediately sends a POST callback to the notify_url you passed when creating the order.
If notify_url is omitted when creating an order, the default webhook URL configured in Merchant Console > API Settings is used instead.
Request Headers
| Header | Meaning |
|---|---|
| Content-Type | Always application/json |
| X-UAPI-Signature | HMAC-SHA256 hex digest (no sha256= prefix) |
| X-UAPI-Timestamp | Unix timestamp (seconds) used as part of the signed payload |
| X-UAPI-Event | Currently always order.paid |
| X-UAPI-Event-ID | Per-event unique ID (16 hex chars); use it to dedupe retries |
Request Body (flat)
{
"status": "paid",
"order_no": "UAPI20260408001",
"merchant_order_id": "ORD-001",
"amount": "100.00",
"chain": "trc20",
"currency": "USDT",
"tx_hash": "0xabcdef1234567890...",
"paid_at": "2026-04-08T10:05:00+00:00"
}
⚠️ Field names: order_no (not order_id), currency (not token), tx_hash (not txid). The body is flat — there is no wrapping event/data object.
Signature Input & Verification
The signed payload is four fields concatenated: order_no + amount + merchant_order_id + timestamp. The secret is your API Key — there is no separate webhook secret.
<?php
$rawBody = file_get_contents('php://input');
$payload = json_decode($rawBody, true);
$signature = $_SERVER['HTTP_X_UAPI_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_UAPI_TIMESTAMP'] ?? '';
$apiKey = 'sk_live_your_api_key';
// reject signatures older than 5 minutes (anti-replay)
if (abs(time() - (int)$timestamp) > 300) {
http_response_code(401); exit('Stale timestamp');
}
$signInput = $payload['order_no']
. $payload['amount']
. $payload['merchant_order_id']
. $timestamp;
$expected = hash_hmac('sha256', $signInput, $apiKey);
if (!hash_equals($expected, $signature)) {
http_response_code(401); exit('Invalid signature');
}
// dedupe on X-UAPI-Event-ID, then process the order
// return HTTP 200 to acknowledge
http_response_code(200); echo 'success';
Response requirement: return HTTP 2xx (or a body containing success). Otherwise UAPI retries up to 3 times (10s timeout each). The X-UAPI-Event-ID header stays constant across retries — use it to dedupe.
Order State Machine
| Status | Description |
|---|---|
| pending | Created and waiting for user payment with the exact amount. |
| paid | On-chain amount and address matched; tx hash recorded and callback sent. |
| expired | Not paid within validity period (usually 10-20 minutes); automatically marked as expired. |
Rate Limits, Idempotency & Amount Precision
The Four Rate-Limit Layers (in order)
| Layer | Scope | Quota | On exceed |
|---|---|---|---|
| 1. Per-IP 黑名单 | Site-wide, by caller IP | Auto-block on repeated failures | 403 IP Blocked: ... |
| 2. Per-endpoint burst | Per endpoint × IP × 60s window | order/create.php: 30 / minorder/status.php: 30 / minorder/dispute.php: 20 / minstore/create_order.php: 20 / min | 429 Too Many Requests |
| 3. Per-order burst | Single order across all IPs × 10s | 40 / 10s | 429 Order polling too frequent |
| 4. Daily quota | Account-level, by Plan | Free 100 / day Pro 20,000 / day Business 50,000 / day | 429 Daily quota exceeded |
Idempotency Guarantees
- Create Order: POSTing the same
merchant_order_idreturns the existing order (sameorder_noandpayment_url) — no duplicate is created. - Webhook: Each event has a unique
X-UAPI-Event-ID; retries reuse the same id — dedupe on it. - Concurrency: Lock by order_no (e.g. Redis) when handling webhooks to serialize per-order work.
Amount Precision & Anti-collision
Request amount is a plain decimal in the range (0, 1,000,000].
Response amount may be your input padded with up to 6 random decimal digits (e.g. 100.00 → 100.001234). This is intentional: the trailing micro-amount lets the on-chain watcher distinguish two simultaneous orders with the same headline price.
Customers must transfer the response amount exactly (a single missing micro-unit causes the watcher to skip the order).
Do NOT truncate the response amount to 2 decimals client-side; show the exact UAPI-returned amount in the QR / instructions.
Supported Networks & Precision
| Network (Slug) | Precision | Token | Notes |
|---|---|---|---|
| trc20 | 6 | USDT | Tron network with fast confirmation and very low fees. |
| bsc | 18 | USDT/BNB | Binance Smart Chain. |
| erc20 | 18 | USDT/ETH | Ethereum mainnet (higher gas fees). |
| polygon | 6/18 | USDT/MATIC | Layer 2 scaling network. |
| solana | 6/9 | USDT/USDC | High-performance non-EVM chain. |
| arbitrum | 18 | USDT | Ethereum L2 rollup network. |
Errors & Error Codes
External API endpoints (X-API-KEY) currently emit errors in two shapes — a short single-field form and a structured status/error/code form. Your client should handle both.
Short form (most auth / rate-limit errors)
{ "error": "Invalid API Key" }
Structured form (business errors)
{
"status": "error",
"error": "Plan expired. Please upgrade/renew.",
"code": "PLAN_EXPIRED"
}
HTTP Status Codes
| Status | Meaning | Common Trigger |
|---|---|---|
| 200 | Success | Order created / data returned |
| 400 | Bad request | Missing field / unsupported chain / USDC on TRC20 |
| 401 | Unauthorized | Missing X-API-KEY header |
| 403 | Forbidden | Invalid API key / suspended account / plan expired / IP blocked / IP not in whitelist / chain not enabled on plan / origin not bound |
| 429 | Rate limited | Daily quota exhausted / per-IP burst / per-order query burst |
| 500 | Server error | Wallet not provisioned / RPC failure / DB exception |
Common error messages & how to fix
| error | How to fix |
|---|---|
| Missing API Key | Add X-API-KEY: sk_live_xxx header |
| Invalid API Key | Key wrong or rotated. Console > API Settings > copy current key |
| IP not in whitelist | Add the caller IP to whitelist in API Security settings |
| Plan expired | Renew or upgrade your plan |
| Access Denied: Chain '...' is not enabled for your current plan | Chain requires a higher plan tier; switch chain or upgrade |
| Access Denied: domain not bound | Bind the request origin in Settings > Websites, or pass `domain` in the JSON body for S2S calls |
| Currency '...' is not supported on chain '...' | USDC is not available on TRC20; pick another chain or USDT |
| Too many requests | Back off and retry; or upgrade plan for higher quota |
Tools & Versions
TLS Requirement
For security, our API requires TLS 1.2 or higher. Older SSL/TLS versions cannot establish a connection.
Data Encoding
All request and response bodies must use UTF-8. Ensure your Content-Type is set to application/json.
Developer FAQ
Can order expiration be customized?
Default validity is 10-20 minutes. For longer expiration, contact support or configure it in Merchant Console > API Settings. Longer windows may increase amount-collision probability.
Why does the actual payable amount include random decimals?
This enables precise order matching in non-custodial wallets. Since multiple transfers may share the same integer amount on-chain, adding a tiny random decimal (e.g., 100.001234) keeps each order unique under your wallet address.
Multi-language Integration Examples
$apiKey = 'sk_live_xxx';
$data = [
'amount' => 100.0,
'chain' => 'trc20',
'merchant_order_id' => 'MY_ORDER_001',
'notify_url' => 'https://yoursite.com/callback',
'domain' => 'yoursite.com'
];
$ch = curl_init('https://uapi.io/api/v1/order/create.php');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-KEY: ' . $apiKey,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$result = json_decode($response, true);
echo $result['data']['payment_url'] ?? 'Create order failed';
const axios = require('axios');
const createOrder = async () => {
const { data } = await axios.post('https://uapi.io/api/v1/order/create.php', {
amount: 100.0,
chain: 'trc20',
merchant_order_id: 'MY_ORDER_001',
notify_url: 'https://yoursite.com/callback',
domain: 'yoursite.com'
}, {
headers: {
'X-API-KEY': 'sk_live_xxx',
'Content-Type': 'application/json'
}
});
console.log(data.data.payment_url);
};