API v1 — YTMarket

REST API for wholesale clients: catalog, orders, balance, webhooks.

Base URL: https://ytmarket.pro/api/v1

⚡ Quick Start — from key to first order in 5 minutes

The minimum chain to start using the API:

  1. Message Telegram support — get `X-API-Key` + (optional) HMAC `secret`. Save them in a password manager.
  2. Try `GET /v1/health` with that key — must return 200.
  3. Browse catalog: `GET /v1/products?min_qty=10&max_price=2` — see all items + `min_quantity` field (minimum batch).
  4. Top up balance: `POST /v1/balance/topup` → send USDT to the returned address. After 5-10 sec post-confirmations the balance updates automatically.
  5. Place an order: `POST /v1/orders` with `payment_method:"balance"`. Get `order_id` + `status: "processing"`.
  6. Poll `GET /v1/orders/{id}` every 5 sec until `status` becomes `completed` (then `items[]` returns account credentials) or `failed` (balance auto-refunded).
  7. Optional: pass `webhook_url` in POST /orders to receive push notifications instead of polling.

1. Getting an API key

Keys are issued by admin only — no self-signup. Message Telegram support with subject "API key" + your email/project. Limit: 60 requests/minute per key (HTTP 429 on exceed). Key + HMAC secret are issued together and shown only once — save immediately. support.

2. Authentication

X-API-Key: YOUR_KEY_HERE

Every request requires the header. Missing or invalid → HTTP 401 Unauthorized.

3. Catalog (READ-ONLY)

GET /v1/health

Key validation + uptime probe.

curl -H "X-API-Key: $KEY" https://ytmarket.pro/api/v1/health
# {"status":"ok","timestamp":1779497200}

GET /v1/products

Catalog with filters. Query params:

  • category, sku
  • min_price, max_price (float, USDT)
  • min_qty (default 1), page (default 1), page_size (default 50, max 200)
curl -H "X-API-Key: $KEY" \
  "https://ytmarket.pro/api/v1/products?min_price=1&page=1&page_size=50"

GET /v1/products/{id}

Single-product details by ID.

GET /v1/categories

Category list with product counts + min prices.

4. Balance & top-up

GET /v1/balance

Current key balance + total spent/topped-up/refunded.

curl -H "X-API-Key: $KEY" https://ytmarket.pro/api/v1/balance
# {"balance_usdt": 50.0, "total_spent_usdt": 12.5,
#  "total_topup_usdt": 62.5, "total_refunded_usdt": 1.2,
#  "currency": "USDT"}

POST /v1/balance/topup

Create a top-up intent → get USDT address. Client picks amount + network. Body:

curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"amount_usdt": 100, "network": "usdt_trc_20"}' \
  https://ytmarket.pro/api/v1/balance/topup

# {"topup_id": "abc123...", "payment_address": "TYz...",
#  "payment_network": "usdt_trc_20", "amount_usdt": 100.0,
#  "expires_at": 1779500800}

After USDT arrives at the address — balance is automatically credited (cron scans incoming TX every 5 sec). Addresses are shared with website purchases (one master wallet per network); intents are distinguished by amount.

Full top-up lifecycle

  1. T+0:00 — client: POST /v1/balance/topup {amount_usdt: 100, network: "usdt_trc_20"}
  2. T+0:00 — API creates `topup_id`, returns `payment_address` (your master) + `amount_usdt: 100` + `expires_at: now+1h`
  3. T+0:30 — client sends 100 USDT (TRC-20) from their wallet to that address
  4. T+0:55 — TRC-20 confirms (~20 sec / 3-5 blocks)
  5. T+1:00 — backend (every 5s) sees incoming TX, searches pending intents by exact amount
  6. T+1:05 — backend matches the deposit by amount ±$0.01 with your intent → BAL +100, status=credited_auto
  7. T+1:05 — client: GET /v1/balance → balance_usdt: 100.0 ✓

📋 Response fields — what each means

topup_idIntent UUID (stored 24h in Redis). Use for status polling or audit.
payment_addressYour master wallet for the chosen network. Same as website purchases. No new address generated per top-up.
payment_networkEcho: usdt_trc_20 / usdt_bep_20 / usdt_polygon / usdt_arbitrum / usdt_ton / usdt_solana.
amount_usdtKey matching tag. Backend looks for incoming TX with exactly this amount (±$0.01).
expires_atUnix epoch +1h. If client doesn't send TX within 1h → intent expires; deposit awaits manual credit via support.

⚠️ Edge cases — what if something is off

Asked $100, sent $100.00
✅ Auto-credited in 5-10 sec
Asked $100, sent $99.99
✅ Credited (tolerance ±$0.01)
Asked $100, sent $98 (under)
❌ Won't auto-match. Contact support — actual amount will be credited manually.
Asked $100, sent $200 (over)
❌ No match (over tolerance), manual credit needed
Asked $100, sent after 2h (expired)
❌ Intent expired; deposit awaits manual credit via support.
Amount collides with a website Order within 15-min window
✅ Defended: dynamic pricing +1%/hit makes amounts unique. Collision probability <1%. Website orders take precedence.
Client sent TX on wrong network (usdt_bep_20 instead of usdt_trc_20)
❌ Deposit arrives on EVM master, but intent waits for TRC-20 → no match. Manual credit + talk to client.

5. Orders (balance-debit)

POST /v1/orders

Create order + auto-debit balance + system fulfilment. On failed/expired/cancelled — balance is auto-refunded (`refunded` field in response and webhook).

curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"product_id": 12345, "quantity": 10,
       "payment_method": "balance",
       "webhook_url": "https://your.app/api-cb"}' \
  https://ytmarket.pro/api/v1/orders

# {"order_id": 789, "status": "processing",
#  "price_usdt": 12.34, "quantity": 10, "refunded": false,
#  "created_at": 1779497200, "updated_at": 1779497200}

If balance < price → HTTP 402 Payment Required with top-up instruction.

⚠️ Minimum batch per product is in `min_quantity` field. If requested `quantity` is below this minimum → HTTP 400 with the exact required amount.

GET /v1/orders/{id}

Order status + items[] with credentials when completed. Key-scoped (404 if not yours).

curl -H "X-API-Key: $KEY" https://ytmarket.pro/api/v1/orders/789

# {"order_id": 789, "status": "completed", "refunded": false,
#  "price_usdt": 12.34, "quantity": 10,
#  "items": [{"login": "...", "password": "...", ...}],
#  "created_at": 1779497200, "updated_at": 1779497500}

6. Webhook notifications

Pass `webhook_url` in POST /v1/orders — we POST JSON there on status change (completed/partial/failed/expired/cancelled/refunded).

POST $YOUR_WEBHOOK_URL
Content-Type: application/json

{
  "order_id": 789,
  "status": "completed",
  "price_usdt": 12.34,
  "quantity": 10,
  "product_id": 12345,
  "items": [{"login": "...", "password": "...", ...}],
  "error_message": null,
  "refunded": false,
  "fired_at": 1779497200
}

Expects HTTP 2xx response. On 5xx/timeout — up to 5 retries with exp-backoff. After 5 fails webhook is marked fired.

7. Usage metrics

GET /v1/usage

Per-key statistics: request count, last activity, total spent, refunded.

curl -H "X-API-Key: $KEY" https://ytmarket.pro/api/v1/usage
# {"request_count": 1234, "last_used_at": 1779497200,
#  "first_used_at": 1779480000, "last_endpoint": "/api/v1/products",
#  "total_spent_usdt": 125.50}

8. Account delivery

Delivery time: 30 seconds — 10 minutes after `status=processing`. Processed async — poll GET /v1/orders/{id} or register webhook.

  1. POST /v1/orders → status=processing
  2. Async processing (30s-10min)
  3. On success → status=completed
  4. GET /v1/orders/{id} → items[]
  5. Webhook fired (if registered)

9. USDT networks & fees

NetworkFeeTimeRecommendation
usdt_trc_20$0.5-11-3 minDefault
usdt_bep_20$0.2-0.530 sec - 2 minCheap + fast
usdt_polygon$0.01-0.051-3 minCheapest
usdt_arbitrum$0.1-0.330 secIf on Arbi
usdt_ton$0.05-0.15-15 secFast
usdt_solana$0.01-0.025-30 secCheap + fast

10. Limits & pricing

  • Rate-limit: 60 requests/min/key (HTTP 429 + Retry-After header)
  • API price = website price (no markup)
  • page_size: 1-200 (default 50)
  • order quantity: 1-10000 units
  • Top-up: 1-100000 USDT
  • Webhook timeout 10s, up to 5 retries
  • Auto-refund: balance returned on failed/expired/cancelled (cron every 30s)

11. Error codes

  • 401invalid X-API-Key
  • 402insufficient balance
  • 404product/order not found
  • 409insufficient stock
  • 422invalid request body
  • 429rate-limit (60/min)
  • 501invoice mode not implemented
  • 503Redis down

11.5 Security (HMAC + IP whitelist)

Additional defenses for production keys. Opt-in via admin request.

HMAC body signing (POST /orders + /topup)

On key mint, admin may issue an HMAC secret (shown once). If a key has a secret, POST requests must include 2 extra headers. Formula:

X-Timestamp = unix epoch seconds (≤5 min drift from server)
X-Signature = hmac_sha256(secret, f"{timestamp}\n{request_body}").hex()
# Python example
import hmac, hashlib, time, json, requests
KEY = "..."; SECRET = "..."
BASE = "https://ytmarket.pro/api/v1"
body = json.dumps({"product_id": 12345, "quantity": 1, "payment_method": "balance"})
ts = str(int(time.time()))
sig = hmac.new(SECRET.encode(), f"{ts}\n{body}".encode(), hashlib.sha256).hexdigest()
r = requests.post(f"{BASE}/orders", data=body, headers={
    "X-API-Key": KEY,
    "Content-Type": "application/json",
    "X-Timestamp": ts,
    "X-Signature": sig,
})
print(r.status_code, r.json())

Protects against: replay attack, MITM tampering, accidental key leak (signature is invalid without secret). If no secret was issued — backward-compat, signature not required.

IP whitelist

Admin can add allowed IPs for a key. If whitelist is empty — no IP check (default). If at least one IP is present — all others get HTTP 403.

Audit log

All admin actions (mint/revoke/credit/topup credited/rotate secret/whitelist) are written to the DB table `api_audit_log`. Admin can list last 50 via bot command `/api_audit` or endpoint `GET /api/admin/api-audit`.

🧪 Try it now — interactive playground

Paste your X-API-Key below and click any button — the request fires directly from this page to our API. For POST endpoints, HMAC signature is computed in the browser automatically (if your key has a secret).

GET https://ytmarket.pro/api/v1/health
🔒 Запрос отправляется напрямую с твоего браузера на https://ytmarket.pro/api/v1. Ключ нигде не сохраняется на сервере.

12. SDK examples

Python (requests)

import requests
KEY = "YOUR_API_KEY"
BASE = "https://ytmarket.pro/api/v1"
headers = {"X-API-Key": KEY}

# 1. List products
products = requests.get(f"{BASE}/products", headers=headers,
                         params={"min_price": 1, "page_size": 50}).json()

# 2. Check balance
bal = requests.get(f"{BASE}/balance", headers=headers).json()
print(f"Balance: {bal['balance_usdt']} USDT")

# 3. Create order (balance-debit)
order = requests.post(f"{BASE}/orders", headers=headers, json={
    "product_id": products["items"][0]["id"],
    "quantity": 1,
    "payment_method": "balance",
    "webhook_url": "https://your.app/cb",
}).json()
print(f"Order #{order['order_id']} {order['status']}")

# 4. Poll until final
import time
while True:
    o = requests.get(f"{BASE}/orders/{order['order_id']}", headers=headers).json()
    if o["status"] in ("completed", "failed", "expired", "cancelled"):
        break
    time.sleep(5)
print(f"Final: {o['status']}, items={o.get('items')}, refunded={o.get('refunded')}")

Node.js (axios)

import axios from "axios";

const KEY = process.env.API_KEY;
const api = axios.create({
  baseURL: "https://ytmarket.pro/api/v1",
  headers: { "X-API-Key": KEY },
});

// 1. List products
const { data: products } = await api.get("/products", { params: { min_price: 1 } });

// 2. Create order
const { data: order } = await api.post("/orders", {
  product_id: products.items[0].id,
  quantity: 1,
  payment_method: "balance",
  webhook_url: "https://your.app/cb",
});

// 3. Poll
let o;
while (true) {
  o = (await api.get(`/orders/${order.order_id}`)).data;
  if (["completed", "failed", "expired", "cancelled"].includes(o.status)) break;
  await new Promise(r => setTimeout(r, 5000));
}
console.log("Final:", o.status, "items:", o.items, "refunded:", o.refunded);

Go (net/http)

package main

import (
  "bytes"; "encoding/json"; "fmt"; "io"; "net/http"
)

func main() {
  body, _ := json.Marshal(map[string]any{
    "product_id": 12345, "quantity": 1, "payment_method": "balance",
  })
  req, _ := http.NewRequest("POST", "https://ytmarket.pro/api/v1/orders", bytes.NewReader(body))
  req.Header.Set("X-API-Key", "YOUR_KEY")
  req.Header.Set("Content-Type", "application/json")
  resp, _ := http.DefaultClient.Do(req)
  defer resp.Body.Close()
  b, _ := io.ReadAll(resp.Body); fmt.Println(string(b))
}

curl

# Full flow in 4 curls
KEY=YOUR_API_KEY
BASE=https://ytmarket.pro/api/v1

curl -H "X-API-Key: $KEY" "$BASE/products?min_price=1&page_size=5"
curl -H "X-API-Key: $KEY" "$BASE/balance"
curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"product_id":12345,"quantity":1,"payment_method":"balance"}' \
  "$BASE/orders"
curl -H "X-API-Key: $KEY" "$BASE/orders/789"

🙋 FAQ — frequently asked questions

What is the difference between X-API-Key and HMAC Secret?

X-API-Key is a public client identifier sent in every request header. Secret is a private key used to sign POST request bodies via HMAC-SHA256, NEVER sent in the request (only used locally to compute X-Signature). If only the key is leaked, the attacker can read catalog/balance but cannot make POST orders/topup.

When do I need the HMAC Secret?

If the admin issued you a secret with the key (default in our flow), the secret is required for POST /orders and /topup. Without a valid signature you get HTTP 401. GET endpoints (health/products/balance/etc) do NOT need the secret — only X-API-Key.

I lost my secret. What now?

Ask admin to call `POST /api/admin/api-keys/{fp}/rotate-secret` — a new secret will be issued, the old one is invalidated immediately. The key itself stays the same.

Can I top up balance without placing an order?

Yes, the two flows are independent. `POST /v1/balance/topup` → get address → send USDT → balance grows. No order required. You can top up once and use for months.

What if I send the wrong amount?

If diff ≤ $0.01 — credited automatically. If more — contact support and the actual amount will be credited manually. See edge cases table in section 4.

Are top-up addresses shared with website?

Yes. One wallet per network (TRC-20/EVM/TON/Solana) serves both API clients and regular website buyers. Intents differ by exact amount. Website orders take precedence.

Why isn't the minimum quantity 1?

Business rule: each product has a `min_quantity` field + an order-total floor (~600₽). The goal is to filter out tiny "pilot" orders that don't cover network fees. API prices match website (no markup).

How do I know when my order is done?

Two ways: (1) poll `GET /v1/orders/{id}` every 5 sec until status becomes `completed`/`failed`/`expired`/`cancelled` — `items[]` appears on completed; (2) webhook — pass `webhook_url` in POST /orders, receive POST with the same JSON on your URL.

What if my order fails?

Balance is **automatically refunded** (`refunded: true` in /orders/{id} + webhook). The refund cron runs every 30 sec. You can retry the same order later or try another `product_id` in the same category.

Where do I see my recent requests / spending?

`GET /v1/usage` shows request_count, last_used_at, total_spent_usdt. `GET /v1/balance` shows total_spent_usdt + total_topup_usdt + total_refunded_usdt.

How does cross-platform dynamic pricing work?

If within the last 15-min window an order with the same amount already existed (on any of our marketplaces + via API), the next order gets +1% (capped at +10%). The purpose is to guarantee amount uniqueness for deposit matching. Visible in `raw_data.dp_hits` of created order.

Is the webhook signed?

Yes, if your key has an HMAC secret — the webhook payload is signed with the same secret. Headers: `X-Webhook-Timestamp`, `X-Webhook-Signature`, `X-Webhook-Algorithm: HMAC-SHA256`. Receiver checks: `hmac_sha256(secret, f"{ts}\n{request.body}").hex() == X-Webhook-Signature` and drift ≤ 5 min.

What if my webhook URL doesn't respond?

Backend retries up to 5 times with exp-backoff (10s timeout each). After 5 failures the webhook is marked fired (not retried). Make your receiver idempotent (by order_id + status).

Can I cancel an order?

No user-facing cancel currently. The order either succeeds (`completed`), or it gets marked `failed`/`expired` (then auto-refund). If you really need to cancel, contact support; admin can mark `cancelled` manually (also triggers refund).

13. Postman / Changelog / Support

Postman collection

Download a ready Postman collection for all endpoints: Download .postman_collection.json

Changelog

  • v1.4 (2026-05-23) — Min-purchase enforcement (per-product min_quantity + 600₽ floor); auto-credit cron 120s→5s; cross-platform dynamic pricing +1%/15min also applies via API; webhook HMAC signing (X-Webhook-Signature header); Try-it playground in /api/docs.
  • v1.3 (2026-05-23) — Auto-refund on failed/expired/cancelled, `refunded` field, status maps paid→processing, `created_at`/`updated_at` in response.
  • v1.2 (2026-05-23) — Balance system, POST /orders with balance-debit, webhook cron.
  • v1.1 (2026-05-23) — Rate-limit 60/min/key, per-key metrics.
  • v1.0 (2026-05-23) — Read-only catalog endpoints (products/categories/health).

Support

API questions: Telegram support. SLA: 30 min during work hours (08:00-22:00 MSK). Telegram.

YTMarket API v1 • ytmarket.pro