Getting started
Responses & errors
Every business response uses one envelope. A zero code means success; anything else is a failure with the reason in msg.
Response envelope
{
"code": 0,
"msg": "success",
"data": {}
}code = 0means success;code != 0means failure.msgcarries the reason.dataholds the business object on success and is usually empty ornullon failure.
Responses are signed — verify the X-Sign header over the raw body before parsing. See Verify responses.
Error codes
| Code | Meaning | What to do |
|---|---|---|
0 | Success | data holds the business object. |
400 | Invalid request format | Malformed JSON or a missing required field. Check the field tables for the endpoint. |
401 | Unauthorized | Missing or invalid auth headers. Start with the signature checklist. |
403 | Forbidden / invalid signature | The signature did not verify, or the merchant is not allowed to call this route. |
422 | Business validation error | Well-formed request rejected by a business rule; msg explains which. |
500 | Internal server error | Retry. Reuse the same merchant reference so a double-submit is caught as 2001 rather than creating a second order. |
1003 | Signature verification failed | Recompute sign_payload and compare byte-for-byte. See troubleshooting. |
2001 | Duplicate order | merchant_order_no was already used. Query the existing order instead. |
2002 | Order not found | No payment matches the given order_no / merchant_order_no. |
2006 | Collection amount outside limits | The amount is outside the minimum/maximum currently allowed for that payment method. |
3003 | Payment method not enabled | The payment_method is not switched on for your account. Contact support. |
5001 | Duplicate payout order | merchant_payout_no was already used. |
5002 | Payout order not found | No payout matches the given payout_no / merchant_payout_no. |
5004 | Insufficient payout balance | Run a precheck; spendable_balance is below total_debit. |
5008 | Payout amount outside limits | The amount is outside the minimum/maximum currently allowed for payouts with that method. |
HTTP status vs. business code
The
code field is the authoritative result. Transport-level codes (400, 401, 403, 500) may also surface as HTTP status codes; business codes (2xxx, 3xxx, 5xxx) arrive in a normal JSON envelope.Amount limits
2006 (collection) and 5008 (payout) mean the amount does not satisfy the amount limits currently in effect for that payment method. They are not signature errors — X-Sign does not need investigation.
{
"code": 2006,
"msg": "Payment amount does not satisfy limits",
"data": null
}- Is
trans_amount.valuebelow the current minimum for this payment method? - Is it above the current maximum?
For payouts, call precheck with the amount first — it returns 5008 for the same condition without creating anything.
Conventions
| Topic | Rule |
|---|---|
| Field names | snake_case everywhere: payment_method, merchant_order_no, trade_info.goods_name. camelCase keys are rejected. |
| Amounts | Decimal strings, e.g. "99.99", inside a trans_amount object with currency and value. Never floats. |
| Currency | 3-letter uppercase ISO code, e.g. USD. |
| Country | 2-letter uppercase ISO code, e.g. US. |
| Timestamps | RFC 3339 strings in response bodies (2026-06-24T10:01:23Z); Unix seconds in X-Timestamp. |
| Your references | merchant_order_no, merchant_refund_no, merchant_payout_no — unique on your side, used for idempotency. |
| Our references | order_no, refund_no, payout_no — assigned by StablePay. Query endpoints accept either yours or ours. |
| Passthrough | metadata is an opaque string returned unchanged in queries and notifications. |
| Statuses | Payments and refunds: PENDING, SUCCESS, FAILED. Payouts add PROCESSING and CANCELED. |