HTTP Status Code Standard¶
Version: 1.0 Last Updated: 2026-04-03 Owner: Platform Team Status: Active
Purpose¶
Standardise HTTP status code usage across all Simpaisa APIs. Legacy endpoints (v1/v2) return 200 for nearly all responses, bundling success/failure in the response body. The /v3/ API MUST use semantically correct HTTP status codes.
Status Code Reference¶
Success Codes (2xx)¶
| Code | Meaning | When to Use |
|---|---|---|
200 OK |
Request succeeded | Successful GET, PUT, PATCH. Idempotent replay of a previously completed request |
201 Created |
Resource created | Successful POST that creates a resource: transaction initiated, merchant created |
202 Accepted |
Async processing started | Async pay-in flow accepted for processing; final result delivered via webhook |
204 No Content |
Success, no body | Successful DELETE, or PUT where no response body is needed |
Client Error Codes (4xx)¶
| Code | Meaning | When to Use | Example Scenario |
|---|---|---|---|
400 Bad Request |
Malformed request | Invalid JSON, wrong data types, missing required fields | amount sent as string instead of number |
401 Unauthorised |
Authentication failed | Missing, expired, or invalid HMAC/RSA signature | Signature mismatch on X-Simpaisa-Signature header |
403 Forbidden |
Authorised but not permitted | Valid auth, but merchant lacks access to this resource or product | Merchant A querying Merchant B's transaction (IDOR prevention) |
404 Not Found |
Resource does not exist | Transaction ID, merchant ID, or endpoint not found | GET /v3/payin/transactions/TXN-NONEXISTENT |
409 Conflict |
State conflict | Idempotency key reused with a different request body | Same Idempotency-Key, different amount |
422 Unprocessable Entity |
Business rule violation | Valid JSON, correct types, but business logic rejects it | Insufficient balance, unsupported operator, amount below minimum, inactive channel |
429 Too Many Requests |
Rate limit exceeded | Merchant exceeds their allocated request rate | Include Retry-After header (seconds) |
Server Error Codes (5xx)¶
| Code | Meaning | When to Use | Example Scenario |
|---|---|---|---|
500 Internal Server Error |
Unexpected failure | Unhandled exceptions, bugs | Nil pointer dereference, database query failure |
502 Bad Gateway |
Upstream error | Channel returned an error response | JazzCash API returned HTTP 500 |
503 Service Unavailable |
Temporarily down | Channel is in maintenance or circuit breaker is open | Easypaisa scheduled maintenance window |
504 Gateway Timeout |
Upstream timeout | Channel did not respond within the configured timeout | bKash did not respond within 30s |
Error Response Body¶
All error responses (4xx, 5xx) MUST return a consistent JSON structure:
{
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "The source account does not have sufficient balance for this transaction.",
"traceId": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"details": [
{ "field": "amount", "issue": "Exceeds available balance of 1200.00 PKR" }
]
}
}
| Field | Required | Description |
|---|---|---|
error.code |
Yes | Machine-readable error code (UPPERCASE_SNAKE_CASE) |
error.message |
Yes | Human-readable explanation (safe for merchant developers) |
error.traceId |
Yes | OpenTelemetry trace ID for support correlation |
error.details |
No | Array of field-level errors for validation failures |
Simpaisa Error Code Mapping¶
| Scenario | HTTP Status | Error Code |
|---|---|---|
| Missing required field | 400 |
MISSING_FIELD |
| Invalid field format | 400 |
INVALID_FORMAT |
| Invalid JSON body | 400 |
MALFORMED_REQUEST |
| Signature verification failed | 401 |
INVALID_SIGNATURE |
| Expired authentication | 401 |
AUTH_EXPIRED |
| Merchant not authorised for resource | 403 |
ACCESS_DENIED |
| Transaction not found | 404 |
TRANSACTION_NOT_FOUND |
| Merchant not found | 404 |
MERCHANT_NOT_FOUND |
| Idempotency key conflict | 409 |
IDEMPOTENCY_CONFLICT |
| Insufficient funds | 422 |
INSUFFICIENT_FUNDS |
| Unsupported operator/channel | 422 |
UNSUPPORTED_CHANNEL |
| Amount below minimum | 422 |
AMOUNT_TOO_LOW |
| Amount above maximum | 422 |
AMOUNT_TOO_HIGH |
| Channel inactive | 422 |
CHANNEL_INACTIVE |
| Merchant account suspended | 422 |
MERCHANT_SUSPENDED |
| Rate limit exceeded | 429 |
RATE_LIMIT_EXCEEDED |
| Internal error | 500 |
INTERNAL_ERROR |
| Channel returned error | 502 |
CHANNEL_ERROR |
| Channel unavailable | 503 |
CHANNEL_UNAVAILABLE |
| Channel timeout | 504 |
CHANNEL_TIMEOUT |
KrakenD Gateway Transformation¶
For /v3/ endpoints, KrakenD passes through the correct HTTP status codes from backend services.
For legacy /v1/ and /v2/ endpoints, KrakenD does not alter the existing behaviour — these continue to return 200 with status in the body for backward compatibility.
/v3/payin/transactions (POST)
→ Backend returns 201 → KrakenD returns 201 to merchant
→ Backend returns 422 → KrakenD returns 422 to merchant
/v1/Transaction/initiate (POST)
→ Backend returns 200 (with status in body) → KrakenD returns 200 (unchanged)
Rate Limit Headers¶
All responses from rate-limited endpoints MUST include:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 23
X-RateLimit-Reset: 1712153040
When 429 is returned, also include:
Retry-After: 42
Migration Path¶
- Phase 1 (current):
/v3/endpoints use correct status codes. Legacy endpoints unchanged. - Phase 2: SDKs abstract status code handling — merchants using SDKs are insulated.
- Phase 3: Deprecation notices on
/v1/and/v2/. Migration guides published. - Phase 4: Legacy endpoints sunset (12-month notice period).
Testing¶
- Every error code in the mapping table MUST have a corresponding integration test.
- Sandbox environment returns all status codes identically to production (see SANDBOX-STANDARD.md).
- SDK tests verify correct exception types are raised for each status code.