API for executing platforms
The platform takes pay-outs from the EmiPay order book, transfers the money to the recipient and sends the receipt. For each completed pay-out the platform gets USDT on its EmiPay balance.
The platform’s cabinet stays: operators see orders, statistics, balance and disputes, and can take a pay-out by hand.
You get the available pay-outs: a list, plus webhooks when they appear and disappear.
The first to take a pay-out gets it, together with the recipient’s card.
You transfer the UAH to the recipient’s card.
You send the receipt — the pay-out completes, USDT is credited.
Access and key
| Base URL | https://partner.emipay.net/api/partner/v1 | All methods |
| API key | Cabinet → “API integration” → a key with the read and execute scopes | Header Authorization: Bearer emi_…. Shown once. |
| Webhook secret | Cabinet → “API integration” → a webhook with your URL | Event signature check (whsec_…). Shown once. |
Keys and webhooks are created by the platform’s supervisor. A key can be restricted to your servers’ IP addresses. Up to 10 active keys per account.
The API is for server-to-server requests only. Never put the key into a web page or a mobile app.
Request format
- Request and response bodies are JSON (
Content-Type: application/json). - Every
POSTcarries anIdempotency-Keyheader (a unique string): a retry with the same key never repeats the action, so a request can be safely repeated after a network error. - Amounts are strings: UAH with 2 decimals (
"1800.00"), USDT with 6 ("38.730671"). Times are Unix milliseconds. - Rate limit — 600 requests per minute per account. Remaining — in
X-RateLimit-Remaining; over the limit —429. - An error is HTTP 4xx with
{"error":"…","request_id":"…"}. Quote therequest_idwhen contacting support.
Turn intake on
/api/partner/v1/intakeWhile intake is off the book is empty and nothing can be taken. GET /profile shows the admission (directions and amount limits), the intake state and the balance.
curl -X POST https://partner.emipay.net/api/partner/v1/intake \
-H "Authorization: Bearer $EMIPAY_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"enabled": true}'const r = await fetch('https://partner.emipay.net/api/partner/v1/intake', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.EMIPAY_KEY, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID() },
body: JSON.stringify({"enabled": true}),
});
const data = await r.json();r = requests.post("https://partner.emipay.net/api/partner/v1/intake",
headers={"Authorization": "Bearer " + KEY, "Idempotency-Key": str(uuid.uuid4())},
json={"enabled": True},
timeout=30)
data = r.json()Order book
/api/partner/v1/orders/available?page=1The initial state of the book is the list of available pay-outs, 50 per page. The book has no recipient data: it appears once you take the order.
curl https://partner.emipay.net/api/partner/v1/orders/available?page=1 \ -H "Authorization: Bearer $EMIPAY_KEY"
const r = await fetch('https://partner.emipay.net/api/partner/v1/orders/available?page=1', {
headers: { Authorization: 'Bearer ' + process.env.EMIPAY_KEY },
});
const data = await r.json();r = requests.get("https://partner.emipay.net/api/partner/v1/orders/available?page=1",
headers={"Authorization": "Bearer " + KEY},
timeout=30)
data = r.json(){
"page": 1,
"total": 1,
"intake": true,
"rows": [
{
"id": "100edd64-1f20-42db-85a8-e52fc4896646",
"num": 102121,
"direction": "payout",
"status": "pending",
"currency": "UAH",
"settlement_currency": "USDT",
"amount": "1800.00",
"settlement_amount": "38.730671",
"reward": "0.000000",
"rate": "46.48",
"expires": 1793228865353,
"created": 1793217888298,
"updated": 1793217888298
}
]
}amount | UAH to pay to the recipient |
settlement_amount | USDT the platform receives for this pay-out |
rate | Your rate: amount ÷ settlement_amount |
expires | Until when the pay-out is available / must be done |
Then keep the book current with the trade.available and trade.unavailable webhooks (see “Webhooks”). Every 1–2 minutes reconcile it with this list in case a delivery was missed.
Take a pay-out
/api/partner/v1/orders/{id}/acceptThe first to take a pay-out (a trader or a platform) gets it. The response is the order with status active and the recipient’s card in recipient.
curl -X POST https://partner.emipay.net/api/partner/v1/orders/100edd64-1f20-42db-85a8-e52fc4896646/accept \
-H "Authorization: Bearer $EMIPAY_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'const r = await fetch('https://partner.emipay.net/api/partner/v1/orders/100edd64-1f20-42db-85a8-e52fc4896646/accept', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.EMIPAY_KEY, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID() },
body: JSON.stringify({}),
});
const data = await r.json();r = requests.post("https://partner.emipay.net/api/partner/v1/orders/100edd64-1f20-42db-85a8-e52fc4896646/accept",
headers={"Authorization": "Bearer " + KEY, "Idempotency-Key": str(uuid.uuid4())},
json={},
timeout=30)
data = r.json(){
"id": "100edd64-1f20-42db-85a8-e52fc4896646",
"num": 102121,
"direction": "payout",
"status": "active",
"amount": "1800.00",
"settlement_amount": "38.730671",
"rate": "46.48",
"recipient": "5375414112345672",
"expires": 1793228865353,
…
}200— it is yours: pay torecipientbeforeexpires.409— already taken — remove it from the book.{"ok":true,"result":"auction","auction_until":…}— several takers at the same moment; byauction_untilthe executor is decided — you gettrade.accepted(yours) ortrade.unavailable(not yours).
Receipt
/api/partner/v1/orders/{id}/receiptAfter the transfer send the receipt — a PDF or an image, up to 10 MB, base64. The order goes to paid, we confirm the pay-out at the source, and it becomes completed — the USDT is credited to the platform’s balance. Every receipt is checked by AI: make sure the recipient’s card / IBAN and the amount match the order and are clearly visible.
curl -X POST https://partner.emipay.net/api/partner/v1/orders/100edd64-1f20-42db-85a8-e52fc4896646/receipt \
-H "Authorization: Bearer $EMIPAY_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"filename": "receipt.pdf", "base64": "JVBERi0xLjQK…"}'const r = await fetch('https://partner.emipay.net/api/partner/v1/orders/100edd64-1f20-42db-85a8-e52fc4896646/receipt', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.EMIPAY_KEY, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID() },
body: JSON.stringify({"filename": "receipt.pdf", "base64": "JVBERi0xLjQK…"}),
});
const data = await r.json();r = requests.post("https://partner.emipay.net/api/partner/v1/orders/100edd64-1f20-42db-85a8-e52fc4896646/receipt",
headers={"Authorization": "Bearer " + KEY, "Idempotency-Key": str(uuid.uuid4())},
json={"filename": "receipt.pdf", "base64": "JVBERi0xLjQK…"},
timeout=30)
data = r.json()Several files for one pay-out: send all but the last with "more": true — the pay-out closes after the last file.
POST /orders/{id}/receipt {"filename": "1.jpg", "base64": "…", "more": true}
POST /orders/{id}/receipt {"filename": "2.jpg", "base64": "…"}Decline
/api/partner/v1/orders/{id}/declineCannot do a pay-out you took — decline it with a reason:
curl -X POST https://partner.emipay.net/api/partner/v1/orders/100edd64-1f20-42db-85a8-e52fc4896646/decline \
-H "Authorization: Bearer $EMIPAY_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"reason": "error", "comment": "…"}'const r = await fetch('https://partner.emipay.net/api/partner/v1/orders/100edd64-1f20-42db-85a8-e52fc4896646/decline', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.EMIPAY_KEY, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID() },
body: JSON.stringify({"reason": "error", "comment": "…"}),
});
const data = await r.json();r = requests.post("https://partner.emipay.net/api/partner/v1/orders/100edd64-1f20-42db-85a8-e52fc4896646/decline",
headers={"Authorization": "Bearer " + KEY, "Idempotency-Key": str(uuid.uuid4())},
json={"reason": "error", "comment": "…"},
timeout=30)
data = r.json()error— taken by mistake: the pay-out goes back to the book for other executors.wrong_requisite— the recipient’s details are wrong (card blocked, does not exist): the pay-out is cancelled.
Your orders and balance
/api/partner/v1/orders?status=active&page=1/api/partner/v1/orders/{id}/api/partner/v1/balance/api/partner/v1/profile/api/partner/v1/events?after=0/api/partner/v1/pingYour orders by status (active, paid, review, disputed, completed, cancelled, expired), one order, the balance and the event log — a fallback to webhooks.
Events
EmiPay sends events to your URL. Answer 2xx within 10 s, otherwise the delivery is retried (10 s, 30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h). At-least-once delivery: skip repeats by EmiPay-Event-Id.
POST https://your-server/emipay
Content-Type: application/json
EmiPay-Event: trade.available
EmiPay-Event-Id: evt_3f2a…
EmiPay-Delivery: 7c1e…
EmiPay-Signature: t=1793218065,v1=<hex HMAC-SHA256(secret, t + "." + body)>
{"id":"evt_3f2a…","type":"trade.available","created":1793218065475,"data":{ …order… }}| Event | When | What to do |
|---|---|---|
trade.available | A pay-out entered the book (new, or returned to the pool) within your admission | Add it to the book; data is the order as in the list |
trade.unavailable | A pay-out left the book: reason = taken (another executor took it), cancelled, expired | Remove it from the book |
trade.accepted | The pay-out is assigned to you (also after an auction) | Pay before expires |
trade.deadline | The time to pay is running out | Send the receipt or decline |
trade.paid | Receipt accepted, confirming | — |
trade.completed | Pay-out completed, USDT credited | — |
trade.review, trade.disputed | The pay-out went to review / a dispute was opened | Contact support, add evidence in the cabinet |
trade.cancelled, trade.expired, trade.recalculated | The order was cancelled / expired / recalculated | Update on your side |
{"id":"evt_…","type":"trade.unavailable","created":1793218065475,"data":{"id":"100edd64-…","num":102121,"reason":"taken"}}Signature check
The signature is HMAC-SHA256 of t + "." + body with the webhook secret. Compute it over the raw request body and reject t older than 5 minutes.
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody — the request body exactly as received (string), header — EmiPay-Signature
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
return v1?.length === expected.length && timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t, v1 = parts.get("t", "0"), parts.get("v1", "")
if abs(time.time() - int(t)) > 300:
return False
expected = hmac.new(secret.encode(), (t + ".").encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)Order statuses
| Status | Meaning |
|---|---|
pending | In the book, nobody has taken it |
active | Taken by you — pay and send the receipt |
paid | Receipt received, confirming at the source |
review | Under review by the administration |
disputed | A dispute is open |
completed | Pay-out completed, USDT credited |
cancelled | Cancelled |
expired | Time is up |
Error codes
| Code | When |
|---|---|
401 | No key, or the key is wrong, expired or revoked |
403 | The key lacks the scope, the request comes from a disallowed IP, or intake is disabled by the administration |
404 | Order not found (or not yours) |
409 | The order’s state does not allow the action — e.g. already taken |
413 | The receipt file is too large |
422 | Invalid request data |
429 | Rate limit exceeded |
Launch checklist
- The key with the
readandexecutescopes is stored in server secrets; your servers’ IPs are set. - The webhook is reachable over HTTPS and answers 2xx within 10 seconds.
- The handler checks
EmiPay-Signatureover the raw body and skips repeats byEmiPay-Event-Id. - The book is built from the list and updated by
trade.available/trade.unavailable; reconciled with the list every 1–2 minutes. - Every
POSThas a uniqueIdempotency-Key. 409on taking (someone else took it) and theauctionanswer are handled.- A receipt is sent only after the actual transfer; several files — with
"more": true. - The first pay-out is a small amount.
EmiPay