Tài liệu API
API cho phép phần mềm của bạn xem số dư, xem danh sách hàng, mua hàng và nhận hàng tự động. Cần tài khoản đã được shop duyệt và đã tạo key tại 🔑 API.
Bắt đầu nhanh
- Đăng nhập shop.apihub247.com → Tài khoản → API → "Xin dùng API". Shop duyệt xong, bấm tạo key (key chỉ hiện một lần - cất ở máy chủ của bạn).
- Nạp tiền vào ví trên website. API mua bằng số dư ví.
- GET /products: lấy id hoặc slug của món cần mua và giá của bạn.
- POST /orders với một Idempotency-Key mới cho mỗi lệnh mua, chờ tối đa 100 giây. delivered = hàng nằm sẵn trong items.
- processing = shop đang lấy hàng: hỏi lại GET /orders/{id} mỗi 10-30 giây. Mất kết nối hoặc 504 → gửi lại đúng key cũ.
Địa chỉ gốc
https://shop.apihub247.com/api/v1Xác thực
X-Api-Key: ah247_...
Authorization: Bearer ah247_...Gửi key trong header X-Api-Key (hoặc Authorization: Bearer). Key sai, đã đổi hoặc bị thu hồi trả 401 invalid_key. Chỉ gọi từ máy chủ của bạn, đừng để key trong trang web hay app của khách.
Mua bằng số dư ví. Nạp ví trên website như bình thường.
Các lệnh
GET /balance
Xem số dư ví.
curl https://shop.apihub247.com/api/v1/balance \
-H "X-Api-Key: ah247_..."Ví dụ
{ "balance": 250000, "currency": "VND" }GET /products
Hàng bạn mua được, với giá của chính bạn. inStock=false là đang hết. Có options thì mỗi gói là { id, label, price }: id 0 là chính món đó ở giá của bạn, id khác là gói bảo hành thêm (gửi id đó ở trường option khi mua).
curl https://shop.apihub247.com/api/v1/products \
-H "X-Api-Key: ah247_..."Ví dụ
{ "products": [ { "id": 12, "slug": "product-slug", "name": "Product name", "category": "Category",
"price": 50000, "inStock": true, "needsEmail": false,
"options": [ { "id": 0, "label": "30 days", "price": 50000 }, { "id": 7, "label": "90 days", "price": 120000 } ] } ] }POST /orders
Mua hàng, trừ ví. Bắt buộc có header Idempotency-Key. product là id hoặc slug, qty từ 1 đến 10, option (mã bảo hành) và email là tuỳ chọn tuỳ món.
curl -X POST https://shop.apihub247.com/api/v1/orders \
-H "X-Api-Key: ah247_..." \
-H "Idempotency-Key: order-0001-abc" \
-H "Content-Type: application/json" \
-d '{"product": "product-slug", "qty": 1}'Ví dụ
{
"orderId": 1234,
"status": "delivered",
"product": "Product name",
"qty": 1,
"total": 50000,
"items": ["account or key text"],
"createdAt": "2026-10-22T10:00:00Z",
"deliveredAt": "2026-10-22T10:00:02Z",
"warrantyDays": 30,
"warrantyMode": "days",
"warrantyMonths": null
}GET /orders/{id}
Xem lại một đơn của bạn.
curl https://shop.apihub247.com/api/v1/orders/1234 \
-H "X-Api-Key: ah247_..."Ví dụ
{
"orderId": 1234,
"status": "delivered",
"product": "Product name",
"qty": 1,
"total": 50000,
"items": ["account or key text"],
"createdAt": "2026-10-22T10:00:00Z",
"deliveredAt": "2026-10-22T10:00:02Z",
"warrantyDays": 30,
"warrantyMode": "days",
"warrantyMonths": null
}GET /orders?limit=20
Các đơn API gần nhất của bạn (limit 1 đến 100).
curl "https://shop.apihub247.com/api/v1/orders?limit=20" \
-H "X-Api-Key: ah247_..."Ví dụ
{ "orders": [ ... ] }Trạng thái đơn: delivered (đã giao, hàng nằm trong items), processing (đã trả tiền, shop đang lấy hàng - hỏi lại bằng GET /orders/{id}), refunded (đã hoàn tiền vào ví).
Đơn có thể kèm thêm: short { got, want, refunded } (giao thiếu, phần thiếu đã hoàn vào ví), refundedBy: "bank" (đơn hoàn về ngân hàng, không vào ví), và các trường bảo hành warrantyDays / warrantyMode (none, days, months, full) / warrantyMonths.
Idempotency-Key
Mỗi lệnh mua cần một Idempotency-Key riêng, 8 đến 128 ký tự gồm chữ, số và . _ : -. Gửi lại đúng key đó (sau khi mất kết nối, hoặc nhận 504 timeout) sẽ trả lại đúng đơn cũ kèm "replayed": true và không bao giờ bị trừ tiền hai lần - đừng tạo key mới khi chưa biết đơn đã tạo hay chưa. Key dùng được trong 24 giờ; dùng lại key cho lệnh mua khác trả 422 idempotency_key_reused. Mọi thời gian (createdAt, deliveredAt) là giờ UTC dạng ISO, ví dụ 2026-10-22T10:00:00Z.
Mã lỗi
{ "error": "insufficient", "total": 50000, "balance": 20000, "shortfall": 30000 }- 400 - Yêu cầu sai: idempotency_key, bad_json, qty…
- 401 - Key sai, đã đổi hoặc bị thu hồi.
- 402 - Số dư không đủ; kèm total, balance, shortfall. Nạp ví trên website rồi gọi lại.
- 404 - Không có đơn hoặc món này.
- 409 - out_of_stock (đang hết hàng), in_progress (lệnh cùng Idempotency-Key đang chạy, thử lại sau ít giây), option_gone (gói bảo hành không còn).
- 422 - idempotency_key_reused: key này đã dùng cho lệnh mua khác; idempotency_key_expired: key đã dùng hơn 24 giờ trước, hãy dùng key mới.
- 429 - rate_limited: gọi quá nhanh, chờ theo header Retry-After (giây).
- 503 - api_off (API tạm dừng) hoặc unavailable (hệ thống bận, thử lại).
- 504 - timeout: lệnh mua chưa có kết quả, đơn có thể đã tạo. Gửi lại đúng Idempotency-Key cũ (hoặc xem GET /orders), không dùng key mới.
Giới hạn
Mỗi key tối đa 60 lệnh gọi mỗi phút, trong đó tối đa 10 lệnh mua mỗi phút, và tối đa 10 sản phẩm mỗi đơn. Không giới hạn tổng số lượng bạn mua.
OpenAPI (Swagger / Postman)
File mô tả API chuẩn OpenAPI 3, giống tài liệu của các nhà cung cấp. Postman: Import → Link → dán địa chỉ dưới đây. Swagger Editor (editor.swagger.io): File → Import URL.
https://shop.apihub247.com/api/v1/openapi.jsonCode mẫu
Mua một món đúng cách: mỗi lệnh mua một key, gửi lại đúng key khi chưa có kết quả, rồi chờ đơn processing. Thay ah247_... bằng key của bạn và product-slug bằng slug lấy từ GET /products.
Python
import time, uuid, requests
BASE = "https://shop.apihub247.com/api/v1"
HEADERS = {"X-Api-Key": "ah247_..."}
def buy(product, qty=1):
key = str(uuid.uuid4()) # one key per purchase
for _ in range(10):
try:
r = requests.post(f"{BASE}/orders", json={"product": product, "qty": qty},
headers={**HEADERS, "Idempotency-Key": key}, timeout=100)
except requests.RequestException:
time.sleep(5); continue # no answer: resend the SAME key
is_json = r.headers.get("content-type", "").startswith("application/json")
err = r.json().get("error") if is_json else None
if r.status_code == 429 or r.status_code >= 500 or err == "in_progress":
time.sleep(int(r.headers.get("Retry-After", 5))); continue
r.raise_for_status() # 402 insufficient, 409 out_of_stock...
order = r.json()
for _ in range(40): # processing: the shop is fetching it
if order["status"] != "processing":
break
time.sleep(15)
order = requests.get(f"{BASE}/orders/{order['orderId']}", headers=HEADERS, timeout=15).json()
return order # delivered: order["items"]
raise RuntimeError("no answer - check GET /orders")
print(requests.get(f"{BASE}/balance", headers=HEADERS, timeout=15).json())
print(buy("product-slug"))Node.js
// Node.js 18+
import { randomUUID } from 'node:crypto';
const BASE = 'https://shop.apihub247.com/api/v1';
const HEADERS = { 'X-Api-Key': 'ah247_...', 'Content-Type': 'application/json' };
const sleep = (s) => new Promise((r) => setTimeout(r, s * 1000));
async function buy(product, qty = 1) {
const key = randomUUID(); // one key per purchase
for (let i = 0; i < 10; i++) {
let r;
try {
r = await fetch(`${BASE}/orders`, { method: 'POST', headers: { ...HEADERS, 'Idempotency-Key': key },
body: JSON.stringify({ product, qty }), signal: AbortSignal.timeout(100_000) });
} catch { await sleep(5); continue; } // no answer: resend the SAME key
const body = await r.json().catch(() => ({}));
if (r.status === 429 || r.status >= 500 || body.error === 'in_progress') {
await sleep(Number(r.headers.get('retry-after')) || 5); continue;
}
if (!r.ok) throw new Error(`${r.status} ${body.error}`); // 402 insufficient, 409 out_of_stock...
let order = body;
for (let j = 0; j < 40 && order.status === 'processing'; j++) {
await sleep(15); // processing: the shop is fetching it
order = await (await fetch(`${BASE}/orders/${order.orderId}`, { headers: HEADERS })).json();
}
return order; // delivered: order.items
}
throw new Error('no answer - check GET /orders');
}
console.log(await (await fetch(`${BASE}/balance`, { headers: HEADERS })).json());
console.log(await buy('product-slug'));PHP
<?php
const BASE = 'https://shop.apihub247.com/api/v1';
const KEY = 'ah247_...';
function call(string $method, string $path, ?array $body = null, ?string $idem = null): array {
$h = ['X-Api-Key: ' . KEY, 'Content-Type: application/json'];
if ($idem) $h[] = 'Idempotency-Key: ' . $idem;
$c = curl_init(BASE . $path);
curl_setopt_array($c, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h,
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 100]);
if ($body !== null) curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($body));
$out = curl_exec($c);
$code = $out === false ? 0 : curl_getinfo($c, CURLINFO_RESPONSE_CODE);
curl_close($c);
return [$code, $out === false ? [] : (json_decode($out, true) ?? [])];
}
function buy(string $product, int $qty = 1): array {
$key = bin2hex(random_bytes(16)); // one key per purchase
for ($i = 0; $i < 10; $i++) {
[$code, $o] = call('POST', '/orders', ['product' => $product, 'qty' => $qty], $key);
if ($code === 0 || $code === 429 || $code >= 500 || ($o['error'] ?? '') === 'in_progress') {
sleep(5); continue; // no answer: resend the SAME key
}
if ($code !== 200) throw new Exception("$code " . ($o['error'] ?? '')); // 402, 409 out_of_stock...
for ($j = 0; $j < 40 && $o['status'] === 'processing'; $j++) {
sleep(15); // processing: the shop is fetching it
[, $o] = call('GET', '/orders/' . $o['orderId']);
}
return $o; // delivered: $o['items']
}
throw new Exception('no answer - check GET /orders');
}
print_r(call('GET', '/balance')[1]);
print_r(buy('product-slug'));
