Create Invoice
POST /api/v1/invoices
| Field | Type | Required | Notes |
|---|---|---|---|
amount | string | yes | Decimal string, e.g. "19.99". Never send floats. |
currency | string | no | USD (default), EUR, GEL |
asset | string | no | USDT (default) |
network | string | no | TRON (default) |
external_id | string | no | Your order id. Unique per merchant. |
description | string | no | Shown on checkout |
customer | object | no | {email, external_id, name, metadata} |
expires_in | integer | no | Seconds, 300–86400 (default 1800) |
metadata | object | no | Returned unchanged in API and webhooks |
success_url / cancel_url | string | no | Where checkout sends the customer afterwards |
webhook_url | string | no | Extra per-invoice webhook target (signed with your merchant secret) |
payment_address_id | string | no | Use a specific registered address instead of the default |
payment_mode | string | no | ONE_PAYMENT_REQUIRED (default) or AGGREGATE_PAYMENTS |
underpayment_tolerance_percent | string | no | e.g. "0.5"; default 0 = exact payment |
Pricing
Fiat is converted with the exchange rate valid at creation and frozen on the invoice (exchange_rate.rate, source, timestamp). The USDT amount is rounded up to 2 decimals so you never receive less than the fiat price.
When another open invoice on the same address already expects the same amount, a tiny unique offset is added (e.g. 19.9901) so on-chain matching stays unambiguous. Always show the customer crypto_amount, never recompute it.
Idempotency
Send Idempotency-Key: <unique per order> on every POST. Repeating the request returns the original response (with Idempotency-Replayed: true). Reusing a key with a different body returns 422 idempotency_key_reused. Keys expire after 24 hours.
Other endpoints
GET /api/v1/invoices?status=paid,pending&external_id=…&created_from=…&per_page=25
GET /api/v1/invoices/{id}
POST /api/v1/invoices/{id}/cancel (only while pending)
Example with PHP:
$response = Http::withToken($secret)
->withHeaders(['Idempotency-Key' => 'order-'.$order->id])
->post('https://api.usdtpay.ge/api/v1/invoices', [
'amount' => (string) $order->total,
'currency' => 'USD',
'external_id' => 'ORDER-'.$order->id,
'success_url' => route('orders.thanks', $order),
])->throw()->json();
return redirect()->away($response['checkout_url']);
Node.js:
const res = await fetch('https://api.usdtpay.ge/api/v1/invoices', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.GATEWAY_SECRET}`, 'Idempotency-Key': `order-${order.id}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: order.total.toFixed(2), currency: 'USD', external_id: `ORDER-${order.id}` }),
});
const invoice = await res.json();
Python:
import requests
invoice = requests.post(
"https://api.usdtpay.ge/api/v1/invoices",
headers={"Authorization": f"Bearer {SECRET}", "Idempotency-Key": f"order-{order.id}"},
json={"amount": str(order.total), "currency": "USD", "external_id": f"ORDER-{order.id}"},
timeout=15,
).json()