API conventions
Base URLs, versioning, the response envelope, the Money type, resource IDs, timestamps, and pagination.
These conventions hold across every endpoint. Read this once and the rest of the API guides will make sense.
Base URLs
| Environment | Base URL |
|---|---|
| Live | https://api.shadhinpay.pay/api/v1 |
| Sandbox | https://sandbox.shadhinpay.pay/api/v1 |
The API is versioned in the path (/api/v1). Within a major version the contract
is additive only — new optional fields and new endpoints may appear, but
existing fields and operations won't be renamed or removed. Build your clients to
ignore unknown fields and default-handle unknown enum values so additive
changes never break you.
The response envelope
Every JSON response is wrapped in a consistent envelope.
A successful response:
{
"status": "success",
"message": "Payment initiated successfully",
"data": {
"paymentId": "PAY_20260517_K8X3M2"
}
}An error response (see Errors for the full list):
{
"status": "error",
"errorType": "VALIDATION_ERROR",
"message": "Invalid payment request",
"data": {
"amount": "Amount must be between 10.00 and 200000.00"
}
}The meaningful payload is always under data. On validation errors, data is a
map of field name to message.
Money
Monetary values are objects — or, on request bodies, decimal strings — never JSON numbers:
{ "amount": "500.00", "currency": "BDT" }amountis a string so decimal places round-trip exactly ("500.00"is distinct from"500") and there's no floating-point rounding error. Parse it with a decimal type in your language, never a float.currencyis always"BDT"in v1.0.- Payment amounts must be between
"10"and"200000"BDT.
Timestamps
All timestamps are ISO-8601 strings in UTC, e.g. 2026-05-17T10:00:00Z.
Resource IDs
IDs that appear in URLs and webhooks are opaque, non-sequential strings — never guessable integers. Examples:
| Resource | Format | Example |
|---|---|---|
| Payment | PAY_<date>_<base36> | PAY_20260517_K8X3M2 |
| Refund | REF_<date>_<base36> | REF_20260517_M4N1J7 |
| Invoice number | INV-<businessId>-<seq> | INV-HB_71c4-00042 |
| Business (external) | HB_<hex> | HB_71c4a09e |
| Client / merchant (external) | HM_<hex> | HM_4f8c2b1a |
Pagination
List endpoints return a paged envelope:
{
"status": "success",
"data": {
"content": [ /* items */ ],
"page": 0,
"size": 20,
"totalElements": 150,
"totalPages": 8,
"isFirst": true,
"isLast": false,
"hasNext": true,
"hasPrevious": false
}
}Control it with query parameters:
| Parameter | Default | Notes |
|---|---|---|
page | 0 | Zero-indexed |
size | 20 | Maximum 100 |
Rate limits
Requests are rate-limited per business, per route. Typical steady-state limits:
| Endpoint | Limit |
|---|---|
POST /payments | 100 / min |
POST .../refunds | 30 / min |
GET /payments/** | 1000 / min |
POST /invoices | 60 / min |
| Everything else | 600 / min |
Exceeding a limit returns 429 with errorType: RATE_LIMIT_EXCEEDED and a
Retry-After header. Back off and retry after the indicated delay.