1. Request headers
All /shop/** endpoints require two headers. Header names are case-insensitive; HTTPS is strongly recommended to prevent header interception.
curl -X GET "https://api.nova.dev/shop/account" \
-H "X-Shop-No: NOVA20260114012345" \
-H "X-Public-Key: 7c9c9e7b-a8d2-4f1c-9b3e-2a91f4d0e7c4"
2. Authentication
The gateway reads X-Shop-No and X-Public-Key, resolves the merchant from cache or database, and verifies that the supplied public key matches the one bound to the shop. Missing header → 400. Mismatch or unknown shop → 401. Disabled merchant or IP not whitelisted → 403. On success the merchant context is injected for downstream services.
3. Unified response envelope
Every business endpoint returns Response<T> with three fields: code (200 = success, anything else = business failure), message, and data (object, array, or paginated structure; may be null on failure).
{
"code": 200,
"message": "operation successful",
"data": {
"id": "card_8fK3",
"status": "ACTIVE"
}
}
{
"code": 999,
"message": "operation failed",
"data": null
}
4. HTTP status codes
200 — request reached the business layer; check response.code. 400 — missing/invalid headers or parameters. 401 — bad shop number or public key. 403 — merchant disabled, or blocked by IP whitelist. 500 — uncaught platform exception. Always inspect the HTTP status first, then response.code.
GET /shop/account
Returns the merchant account profile: shop number, display name, account currencies, limits, KYB tier, and the timestamp of the last successful authentication.
GET /shop/card/bin/list
Lists the BIN segments currently open for this merchant. Use the returned bin + currency tuple as input to POST /shop/card/create.
GET /shop/card/bin/list
{
"code": 200,
"message": "operation successful",
"data": [
{ "bin": "428392", "brand": "VISA", "currency": "USD", "type": "VIRTUAL" },
{ "bin": "555512", "brand": "MASTERCARD", "currency": "USD", "type": "VIRTUAL" },
{ "bin": "517843", "brand": "MASTERCARD", "currency": "EUR", "type": "VIRTUAL" }
]
}
POST /shop/card/create
Issues a new virtual card on the chosen BIN. The created amount is debited from the merchant's funding account in the same currency. Result is delivered both synchronously and asynchronously via the card-create callback.
POST /shop/card/create
Content-Type: application/json
X-Shop-No: NOVA20260114012345
X-Public-Key: 7c9c9e7b-...
{
"bin": "428392",
"currency": "USD",
"amount": 5000,
"remark": "ads-google-q2",
"callback_url": "https://merchant.example.com/hooks/card"
}
GET /shop/card/info
Retrieves the full card object by cardNo, including PAN reference (PCI-scoped), last4, status, balance and currency.
GET /shop/card/info?cardNo=tok_8fK3...
{
"code": 200,
"message": "operation successful",
"data": {
"cardNo": "tok_8fK3a91c",
"last4": "4242",
"status": "ACTIVE",
"balance": 5000.00,
"currency": "USD",
"createdAt":"2026-06-05T12:04:17Z"
}
}
GET /shop/card/balance
Returns the live spendable balance. For bulk reads use POST /shop/card/balance/batch with up to 100 cardNos per call.
GET /shop/card/balance?cardNo=tok_8fK3a91c
{ "code": 200, "data": { "balance": 4127.55, "currency": "USD" } }
POST /shop/card/freeze · /shop/card/unfreeze
Toggles the card to FROZEN or back to ACTIVE. Idempotent on (cardNo, state). Authorization attempts on a frozen card return decline code 57 (transaction not permitted).
POST /shop/card/freeze
{ "cardNo": "tok_8fK3a91c", "reason": "user_requested" }
→ { "code": 200, "message": "operation successful", "data": null }
POST /shop/card/cancel
Permanently cancels the card. Remaining balance is refunded to the merchant funding account in the original currency. Irreversible.
POST /shop/card/cancel
{ "cardNo": "tok_8fK3a91c" }
→ { "code": 200, "message": "operation successful", "data": null }
POST /shop/card/transfer
Tops up (direction=IN) or withdraws (direction=OUT) funds between the merchant account and the card. Same-currency transfers settle synchronously; cross-currency transfers expose the executed FX rate in the response.
POST /shop/card/transfer
{
"cardNo": "tok_8fK3a91c",
"amount": 250.00,
"currency": "USD",
"direction":"IN"
}
POST /shop/remittance/create
Initiates a global remittance via SWIFT, SEPA or local rails depending on corridor. Call GET /shop/remittance/dynamic-fields first to obtain the required beneficiary fields for the destination country.
POST /shop/remittance/create
{
"payer_id": "biz_91823",
"beneficiary": {
"name": "ACME LTD",
"country": "GB",
"bank_swift": "BARCGB22",
"account_no": "GB29NWBK60161331926819"
},
"amount": 12500.00,
"currency": "USD",
"purpose": "INVOICE_SETTLEMENT"
}
GET /shop/remittance/query
Returns the lifecycle state of a remittance: SUBMITTED → SCREENING → IN_FLIGHT → SETTLED or RETURNED. Status changes are also pushed via the remittance-status callback.
GET /shop/remittance/query?txId=remit_7c2a91d
{
"code": 200,
"data": {
"txId": "remit_7c2a91d",
"status": "SETTLED",
"amount": 12500.00,
"currency": "USD",
"settledAt":"2026-06-05T14:22:08Z"
}
}
GET /shop/bill/list
Paginated reconciliation export covering all account movements: card authorizations, settlements, remittance debits, fees and reversals. Cursor-stable across new inserts.
GET /shop/bill/list?startDate=2026-06-01&endDate=2026-06-30&page=1&size=50
{
"code": 200,
"data": {
"total": 1248,
"list": [
{ "billId":"b_01H8...", "type":"CARD_AUTH", "amount":-12.50, "currency":"USD", "ts":"2026-06-05T11:02:14Z" },
{ "billId":"b_01H9...", "type":"REMIT_OUT", "amount":-12500.00,"currency":"USD","ts":"2026-06-05T14:22:08Z" }
]
}
}
Callback signature flow
Every callback carries X-Sign and X-Timestamp headers. Recompute sha256(rawBody + timestamp + your_private_key) and compare in constant time. Acknowledge with HTTP 200 + { code: 200 } within 5 seconds; the platform retries with exponential backoff up to 24 hours.
// 1. Concatenate body + timestamp + your shop private key
// 2. SHA-256 the result → hex string → compare with X-Sign header
const expected = sha256(rawBody + timestamp + SHOP_PRIVATE_KEY);
if (expected !== req.headers["x-sign"]) {
return res.status(401).send("invalid signature");
}
// Acknowledge within 5s, idempotent on (event_id)
res.status(200).json({ code: 200 });