When an API request fails, ShipSaving returns a structured error response with a machine-readable code and a human-friendly msg. This page documents the error response format, HTTP status codes and rate limits.
All API responses — including errors — share the same JSON structure:
Success response:
{
"code": "ok",
"msg": "ok",
"data": { ... }
}Error response:
{
"code": "shipment.address.city_can_not_be_null",
"msg": "City is required.",
"data": null
}| Field | Type | Description |
|---|---|---|
code | string | "ok" on success; a dot-separated error code on failure (e.g. shipment.buy.pay_shipment_failed). |
msg | string | A human-readable message describing the result. |
data | object / null | The response payload on success; null on failure. |
| Code | Meaning | When It Happens |
|---|---|---|
| 200 | OK | Request succeeded. Check the code field — "ok" means success. |
| 400 | Bad Request | The request contains invalid parameters, missing required fields, or a business logic violation. The code field contains a specific error code. |
| 401 | Unauthorized | Access token is missing, expired, or invalid. Obtain a new token via /oauth2/token. |
| 429 | Too Many Requests | You have exceeded the rate limit. |
| 500 | Internal Server Error | An unexpected error occurred on our side. If this persists, contact support. |
Error codes follow a dot-separated naming pattern that indicates the domain and specific issue:
{domain}.{subdomain}.{detail}For example:
shipment.address.city_can_not_be_null— Shipment domain → Address subdomain → City is missingcommon.transaction.wallet_balance_not_enough— Common domain → Transaction subdomain → Insufficient balance
This pattern helps you programmatically categorize errors in your integration.
The /oauth2/token endpoint follows the OAuth 2.0 error format and does not use the dot-separated convention above. See Authentication for details.
Rate limits are applied at three levels:
| Level | Scope | Description |
|---|---|---|
| App Key | Per API key | Each app_key has its own allowance. One busy integration won't affect your other keys. |
| Company | Per account | All API keys under the same company share a combined limit, even if you use multiple keys. |
| Global | Platform-wide | A system-wide cap across all users. If the platform is under heavy load, requests may be rejected even if your key has room. |
A request must pass all applicable levels. If any one of them is exceeded, the request is rejected.
Limits differ by endpoint and account tier, and may change over time. If you need higher throughput, contact your account manager.
When you exceed the limit, the API returns HTTP 429 with error code common.check.rate_limit:
{
"code": "common.check.rate_limit",
"msg": "request too frequent",
"data": null
}Check the
codefield, not just HTTP status.
A200response with"code": "error"is still an error. Always checkcode == "ok"before processingdata.Handle authentication errors gracefully.
There are two types of401errors:
Invalid credentials: If
/oauth2/tokenreturns401with{"error": "invalid_client"}, yourapp_keyorapp_secretis incorrect. Double-check your credentials — do not retry.Expired access token: If any other API endpoint returns
401with an empty body, your access token has expired. Request a new token from/oauth2/tokenand retry the original request.
Implement retry with backoff for transient errors.
For errors likecommon.redis_lock.system_busy,shipment.pay.get_pay_lock_failed, orcommon.check.rate_limit, retry with exponential backoff:Wait 1s → retry → wait 2s → retry → wait 4s → retry → give upDo not retry validation errors.
Errors in theshipment.address.*,shipment.package.*, andshipment.custom.*categories indicate invalid input. Fix the request parameters before retrying.Use
platform_uk_idto prevent duplicate shipments.
If you receive a timeout or ambiguous response during label purchase, query the shipment status before retrying. Theshipment.current_platform_uk_id_already_create_shipmenterror protects against duplicate charges.