Simpaisa API Architectural Standards¶
| Owner | Classification | Review Date | Status |
|---|---|---|---|
| Architecture Practice | Internal | April 2027 | Active |
Simpaisa API Architectural Standards¶
Version: 1.0.0
Effective Date: 2026-04-01
Owner: CDO / Architecture
Status: Active
Applicability: All Simpaisa API products — Pay-Ins, Pay-Outs (Disbursements), Remittances, Cards, and any future API families
Compliance: All new API endpoints MUST comply with these standards. Existing endpoints MUST comply by the next major version. Exceptions require written CDO approval with a documented migration timeline.
Table of Contents¶
1. Design Philosophy¶
Simpaisa APIs handle real money. The cost of ambiguity, inconsistency, or missing documentation is measured in duplicate payments, failed integrations, and lost merchant trust. These standards exist to prevent that.
Principles:
-
Contract over implementation. API docs describe what the merchant sees (inputs, outputs, behavior, SLAs), never how the system works internally (database tables, schedulers, cache layers).
-
Consistency across products. A merchant integrating Pay-Ins and Pay-Outs should feel like they are using one platform, not two different companies' APIs.
-
Explicit over implicit. Every behavior must be documented. If a rate quote expires after 30 seconds, say so. If webhooks retry 3 times, say so. If OTPs expire in 5 minutes, say so.
-
Safe by default. Idempotency, signature verification, TLS, rate limiting — these are not optional features, they are baseline requirements for a payment API.
-
Machine-readable specifications. OpenAPI 3.1 is the single source of truth. Documentation, SDKs, validation rules, and contract tests all generate from the spec.
2. OpenAPI Specification¶
Addresses: SA Audit Finding 10, GitBook Findings 6, 7, 13
2.1 OpenAPI as Source of Truth¶
All Simpaisa APIs MUST be defined in OpenAPI 3.1 specification files. The OpenAPI spec is the canonical source of truth — not markdown docs, not GitBook pages, not Confluence.
One spec file per API product:
| File | Product |
|---|---|
payin-api.yaml |
Pay-Ins (Wallets, Cards, IBFT, Hosted, Refunds) |
payout-api.yaml |
Pay-Outs (Disbursements) |
remittance-api.yaml |
Remittances |
cards-api.yaml |
Cards (Payments, Inquiry, Capture, Void, Finalize, Refunds) |
2.2 What Generates from the Spec¶
| Artifact | Tool |
|---|---|
| Developer portal | Swagger UI or Redocly |
| Client SDKs | openapi-generator (Java, Python, Node.js, PHP, C#) |
| Spec linting | Spectral (Stoplight) with custom Simpaisa ruleset |
| Contract tests | Schemathesis, Dredd, or Prism |
| Mock server | Prism (for merchant sandbox testing) |
2.3 Spec Requirements¶
Every endpoint in the OpenAPI spec MUST include:
-
Summary and description
-
All request headers (auth, content-type, idempotency key)
-
Request body schema with
requiredfields marked, types, constraints (minLength,maxLength,pattern,enum,minimum,maximum) -
Response schemas for every returned HTTP status code (success and each error case)
-
At least one
examplefor request and response (success and one failure) -
securityscheme reference -
tagsfor product grouping
2.4 Linting Rules (Spectral)¶
A custom Spectral ruleset MUST enforce these standards in CI. Any PR that modifies an OpenAPI spec file MUST pass linting before merge. Minimum rules:
-
Every POST that creates a resource has an
Idempotency-Keyheader parameter -
Every response includes
traceId -
Every 4xx/5xx response uses the standard error schema
-
Every list endpoint has
pageandlimitquery parameters -
Every response includes rate limit headers
-
No
$refto internal-only schemas in merchant-facing specs -
All paths start with
/v{N}/
3. URL Design¶
Addresses: SA Audit Findings 7, 11; GitBook Findings 8, 9
3.1 Standard URL Pattern¶
All API endpoints MUST follow this pattern:
https://api.simpaisa.com/v{version}/{product}/{merchantId}/{resource}[/{action}]
Examples:
POST /v1/disbursements/{merchantId}/transactions/initiate
GET /v1/disbursements/{merchantId}/transactions/{transactionId}
GET /v1/disbursements/{merchantId}/transactions/{transactionId}/status
GET /v1/disbursements/{merchantId}/banks
GET /v1/disbursements/{merchantId}/balance
POST /v1/remittances/{merchantId}/transactions/initiate
GET /v1/remittances/{merchantId}/transactions/{transactionId}
GET /v1/remittances/{merchantId}/fx-rates
POST /v1/payin/{merchantId}/transactions/initiate
POST /v1/payin/{merchantId}/transactions/{transactionId}/verify
POST /v1/cards/{merchantId}/payments/initiate
POST /v1/cards/{merchantId}/payments/{paymentId}/capture
3.2 URL Rules¶
| Rule | Standard | Example |
|---|---|---|
| Base URL | Single domain: api.simpaisa.com |
Not 7 different subdomains |
| Path casing | kebab-case | /fx-rates not /getFxRate or /FxRates |
| Resource names | Plural nouns | /transactions not /transaction |
| Actions | Verb suffix only when necessary | /initiate, /verify, /capture |
No /list suffix |
GET on a collection implies listing | /banks not /banks/list |
| merchantId | In the URL path, validated against auth | Not in the request body |
| No query params for sensitive data | Account numbers, IBANs in body, not URL | Security: URLs appear in logs |
3.3 Base URL Consolidation¶
Target state: All products served from a single domain with path-based routing.
| Environment | Base URL |
|---|---|
| Production | https://api.simpaisa.com |
| Sandbox | https://sandbox.simpaisa.com |
All products (Pay-Ins, Pay-Outs, Remittances, Cards) differentiated by path, not subdomain. The commerceplex.com domain for Remittances MUST be migrated to simpaisa.com.
3.4 merchantId Validation¶
The merchantId in the URL path MUST be validated against the authenticated merchant's identity. If merchant A authenticates and uses merchant B's ID in the URL, the API MUST return 403 Forbidden. This prevents IDOR (Insecure Direct Object Reference) attacks.
4. HTTP Methods¶
Addresses: SA Audit Finding 8
4.1 Method Semantics¶
| Method | Usage | Idempotent | Safe | Cacheable |
|---|---|---|---|---|
| GET | Read a resource or list resources | Yes | Yes | Yes |
| POST | Create a resource or trigger an action | No (unless idempotency key) | No | No |
| PUT | Full replace of a resource | Yes | No | No |
| PATCH | Partial update of a resource | No | No | No |
| DELETE | Remove a resource | Yes | No | No |
4.2 Rules¶
-
GET for reads. All query/fetch/list/status operations MUST use GET. Exceptions: if the query payload contains sensitive data (account numbers, IBANs) that cannot be safely placed in query parameters, use
POST /resource/searchexplicitly named as a search operation. -
POST for state changes only. Transaction initiation, registration, confirmation, refunds — all state-changing operations use POST.
-
No POST for reads. Endpoints like
fetch-account,getFxRate,banks/listMUST be GET. -
No verb-based URLs for CRUD. Use
DELETE /transactions/{id}notPOST /transactions/{id}/delete.
4.3 Migration from POST-for-Reads¶
Changing an HTTP method is a breaking change. When migrating existing POST-to-GET endpoints:
-
Introduce the GET endpoint under a new API version
-
Keep the POST endpoint on the old version with a
Sunsetheader -
Run both versions in parallel for minimum 6 months
-
Do NOT use HTTP 301/302 redirects — they can change POST to GET in non-compliant HTTP clients, which will break payment calls
5. Versioning¶
Addresses: SA Audit Finding 13; GitBook Finding 23
5.1 Versioning Strategy¶
URL path versioning: /v1/, /v2/, etc.
All API URLs MUST include a version prefix. The version number is a single integer — no minor or patch versions in the URL.
/v1/disbursements/{merchantId}/transactions/initiate
/v2/disbursements/{merchantId}/transactions/initiate
Do NOT use header-based versioning (version: 3.0) as the primary mechanism. If a version header currently exists for backward compatibility, document it but deprecate it in favor of path versioning.
5.2 What Constitutes a Breaking Change¶
Breaking (requires new version):
-
Removing or renaming a field from a response
-
Changing a field's type (string → integer)
-
Changing the URL path or HTTP method
-
Changing the authentication mechanism
-
Changing error code values or meanings
-
Removing an endpoint
-
Making a previously optional request field required
Non-breaking (same version):
-
Adding a new optional field to request or response
-
Adding a new endpoint
-
Adding a new error code
-
Adding new enum values to existing fields
-
Increasing rate limits
-
Adding new webhook event types
5.3 Deprecation Policy¶
| Phase | Action | Timeline |
|---|---|---|
| Announcement | Email merchants + changelog entry | Day 0 |
| Sunset header | Add Sunset: <date> header to old version responses |
Day 0 |
| Migration guide | Publish guide with side-by-side old/new examples | Day 0 |
| Warning logs | Log usage of deprecated version for internal tracking | Day 0 |
| Migration support | Active outreach to high-volume merchants | Month 1-4 |
| End of life | Reject requests to old version with 410 Gone |
Month 6+ |
Minimum deprecation window: 6 months for all changes. 12 months for fundamental changes (auth mechanism, URL structure).
6. Authentication & Security¶
Addresses: SA Audit Finding 5; GitBook Finding 1
6.1 Unified Authentication Standard¶
All Simpaisa API products MUST use the same authentication mechanism: RSA-SHA256 request signing with mutual TLS for server-to-server communication.
No product may use session-based authentication (JSESSIONID) or plain API key authentication without signing. These are not suitable for payment APIs.
6.2 Authentication Headers¶
Every authenticated API request MUST include:
| Header | Format | Description |
|---|---|---|
X-Merchant-Id |
String | Merchant identifier |
X-Timestamp |
ISO 8601 UTC | Request timestamp (e.g., 2026-04-01T12:00:00Z) |
X-Signature |
Base64-encoded RSA-SHA256 | Signature over the canonical request string |
Content-Type |
application/json |
Request body format |
6.3 Signature Generation¶
The signature is computed as:
canonical_string = HTTP_METHOD + "
"
+ REQUEST_PATH + "
"
+ X-Timestamp + "
"
+ SHA256(request_body)
signature = RSA_SHA256_SIGN(canonical_string, merchant_private_key)
encoded = BASE64_ENCODE(signature)
Requirements:
-
RSA key size: minimum 2048-bit, PKCS#8 format
-
Hash algorithm: SHA-256
-
Signature encoding: Base64
6.4 Timestamp Validation¶
-
Clock skew tolerance: 5 minutes
-
Requests with timestamps older than 5 minutes MUST be rejected with
401 Unauthorizedand error codeTIMESTAMP_EXPIRED -
This prevents replay attacks
6.5 Key Management¶
The API documentation MUST cover:
-
Credential provisioning: How merchants obtain their
merchantId, RSA key pairs, and any API secrets -
Key rotation: How to rotate keys without downtime (support two active keys during rotation, deactivate old key after confirmation)
-
Key revocation: How to immediately revoke a compromised key
-
IP whitelisting: Optional allowlist of merchant IP addresses for additional security
6.6 Code Samples¶
Authentication documentation MUST include working signature generation code samples in at minimum:
-
Java
-
Python
-
Node.js
-
PHP
-
C#
Each sample must be copy-paste runnable with test credentials.
7. Request Design¶
Addresses: SA Audit Finding 10; GitBook Findings 15, 21
7.1 Request Body Format¶
All request bodies MUST be flat JSON. No request wrapper object.
Correct:
{
"reference": "PAY-20260401-001",
"amount": "50000.00",
"currency": "PKR",
"beneficiaryAccount": "03001234567"
}
Incorrect:
{
"request": {
"reference": "PAY-20260401-001",
"amount": "50000.00"
}
}
Products currently using request wrappers (Pay-Outs, Remittances) MUST migrate to flat JSON in the next major version.
7.2 Field Naming¶
-
Use camelCase for all JSON field names:
merchantId,transactionId,beneficiaryName -
Be consistent: the same concept uses the same field name across all products (see Section 26: Naming Conventions)
-
Never use abbreviations that are not universally understood:
txnId→transactionId,amt→amount
7.3 Required vs Optional¶
Every field in the OpenAPI spec MUST be marked required or optional. Optional fields MUST document their default value or behavior when omitted.
8. Response Design¶
Addresses: SA Audit Findings 2, 10; GitBook Findings 3, 6, 7
8.1 Standard Success Response¶
All successful responses MUST follow this structure:
{
"data": {
"transactionId": "TXN-2026-04-01-0001",
"status": "INITIATED",
"reference": "PAY-20260401-001",
"amount": "50000.00",
"currency": "PKR",
"createdAt": "2026-04-01T12:00:00Z"
},
"traceId": "abc-123-def-456"
}
Rules:
-
The response payload is always under a
datakey for single resources -
For list responses,
datais an array with a siblingpaginationobject (see Section 12) -
Every response includes a
traceIdthat maps to the internal distributed trace (OpenSearch/Jaeger) for debugging -
No
success: trueboolean field. HTTP status codes are the source of truth -
No
responsewrapper object
8.2 Response Schema Completeness¶
Every endpoint MUST document the full response schema for:
-
Success response (all fields, types, whether each is always present or conditional)
-
Error response for each relevant HTTP status code
-
Which request fields are echoed back in the response
9. Error Handling¶
Addresses: SA Audit Findings 2, 6; GitBook Findings 3, 4
9.1 Standard Error Response Schema¶
All error responses (HTTP 4xx and 5xx) MUST use this schema:
{
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Merchant balance is insufficient for this disbursement",
"details": [
{
"field": "amount",
"issue": "Requested 50000.00 PKR but available balance is 12340.00 PKR"
}
]
},
"traceId": "abc-123-def-456",
"timestamp": "2026-04-01T12:00:00Z"
}
9.2 Error Schema Rules¶
| Field | Required | Description |
|---|---|---|
error.code |
Yes | Machine-readable error code, UPPER_SNAKE_CASE |
error.message |
Yes | Human-readable description, safe to display to merchant developers |
error.details |
No | Array of field-level errors for validation failures |
error.details[].field |
Yes (if details present) | The request field that caused the error |
error.details[].issue |
Yes (if details present) | Description of what's wrong with the field |
traceId |
Yes | Distributed trace identifier |
timestamp |
Yes | ISO 8601 UTC timestamp of the error |
9.3 Error Code Conventions¶
Error codes MUST be:
-
Machine-readable:
INSUFFICIENT_BALANCE, not"Your balance is too low" -
UPPER_SNAKE_CASE:
INVALID_ACCOUNT,RATE_LIMIT_EXCEEDED -
Product-agnostic where possible: Use
INVALID_ACCOUNTacross all products, notPAYIN_INVALID_ACCOUNT -
Documented per endpoint: Every endpoint lists all possible error codes
9.4 Standard Error Codes¶
These error codes MUST be used consistently across all products:
| Error Code | HTTP Status | Meaning |
|---|---|---|
INVALID_REQUEST |
400 | Malformed request body or missing required field |
AUTHENTICATION_FAILED |
401 | Missing, invalid, or expired credentials |
TIMESTAMP_EXPIRED |
401 | Request timestamp outside clock skew tolerance |
INVALID_SIGNATURE |
401 | RSA signature verification failed |
FORBIDDEN |
403 | Authenticated but not authorized for this resource |
MERCHANT_MISMATCH |
403 | merchantId in URL does not match authenticated merchant |
RESOURCE_NOT_FOUND |
404 | Transaction, account, or resource does not exist |
IDEMPOTENCY_CONFLICT |
409 | Same idempotency key with different request body |
DUPLICATE_REFERENCE |
409 | Reference already exists for this merchant |
VALIDATION_ERROR |
422 | Business rule validation failure |
INSUFFICIENT_BALANCE |
422 | Merchant balance too low for this transaction |
INVALID_ACCOUNT |
422 | Beneficiary account invalid or not found |
CHANNEL_UNAVAILABLE |
422 | Payment channel (e.g., Easypaisa, 1Link) is down |
QUOTE_EXPIRED |
422 | FX rate quote has expired |
AMOUNT_OUT_OF_RANGE |
422 | Amount below minimum or above maximum |
OTP_EXPIRED |
422 | OTP has expired |
OTP_INVALID |
422 | Incorrect OTP provided |
OTP_MAX_ATTEMPTS |
429 | Maximum OTP verification attempts exceeded |
RATE_LIMIT_EXCEEDED |
429 | Too many requests |
INTERNAL_ERROR |
500 | Unexpected server error |
UPSTREAM_ERROR |
502 | Payment channel returned an unexpected error |
SERVICE_UNAVAILABLE |
503 | System is temporarily unavailable |
9.5 Simpaisa-Specific Status Code Mapping¶
Products currently using numeric status codes (0000, 0001, 0006, 0054, etc.) MUST map these to the standard error codes above. A mapping table MUST be published so merchants can migrate their error handling.
10. HTTP Status Codes¶
Addresses: SA Audit Finding 6; GitBook Finding 4
10.1 Required HTTP Status Codes¶
Every endpoint MUST document which HTTP status codes it returns. The API MUST use proper HTTP status codes — not HTTP 200 for everything with an internal error code.
| Status | When to Use |
|---|---|
| 200 OK | Successful GET, successful POST that returns existing resource (idempotent replay) |
| 201 Created | Successful POST that creates a new resource |
| 204 No Content | Successful DELETE |
| 400 Bad Request | Malformed JSON, missing Content-Type, syntactically invalid request |
| 401 Unauthorized | Missing, invalid, or expired authentication credentials |
| 403 Forbidden | Authenticated but not authorized (e.g., merchantId mismatch) |
| 404 Not Found | Resource does not exist |
| 409 Conflict | Idempotency key reuse with different params, duplicate reference |
| 422 Unprocessable Entity | Business validation failures (insufficient balance, invalid account, expired quote) |
| 429 Too Many Requests | Rate limit exceeded. MUST include Retry-After header |
| 500 Internal Server Error | Unexpected server error |
| 502 Bad Gateway | Upstream payment channel error |
| 503 Service Unavailable | System temporarily unavailable. MUST include Retry-After header |
10.2 HTTP 200 for Errors (Legacy)¶
If a product currently returns HTTP 200 for all responses (including errors) with the error indicated by an internal status code:
-
Document this behavior explicitly so merchants don't waste time debugging
-
Migrate to proper HTTP status codes in the next major version
-
During the migration period, include both the HTTP status code and the legacy internal status code in the response body
11. Idempotency¶
Addresses: SA Audit Finding 1; GitBook Finding 2
11.1 Scope¶
Every state-changing endpoint (POST, PUT, PATCH, DELETE) that initiates, modifies, or reverses a financial transaction MUST support idempotency.
Mandatory endpoints include:
-
Transaction initiation (all products)
-
Transaction verification/confirmation
-
Refund initiation
-
Customer registration
-
FX quote confirmation
11.2 Idempotency Key Header¶
Idempotency-Key: <UUID v4>
The merchant provides a unique key in the Idempotency-Key request header. UUID v4 format is recommended but any unique string up to 128 characters is accepted.
11.3 Server Behavior¶
| Scenario | Response |
|---|---|
| First request with key K | Process normally, store (merchantId, K, response). Return 201 Created |
| Repeat request with key K, same body | Return stored response with 200 OK. Do NOT reprocess |
| Repeat request with key K, different body | Return 409 Conflict with error code IDEMPOTENCY_CONFLICT |
| Key K after expiry window | Process as new request |
11.4 Idempotency Window¶
Keys MUST be stored for a minimum of 48 hours. After 48 hours, the same key may be reused for a new request. Document this window in the API spec.
11.5 Storage¶
Idempotency keys MUST be stored per-merchant to prevent cross-merchant collisions. Implementation options (engineering decision):
-
Per-service MySQL with index on
(merchant_id, idempotency_key)and TTL cleanup -
Redis with TTL for lower latency at high volume
-
API Gateway layer (shared, DRY)
11.6 Existing reference Field¶
If a product uses a reference field as a unique identifier, document clearly whether it provides idempotent behavior:
-
If yes: document that same reference + same params returns original response (same as idempotency key behavior)
-
If no (returns error like
REFERENCE_ALREADY_EXISTS): the merchant cannot distinguish "my first request succeeded" from "my first request failed and the reference was still consumed." This MUST be fixed.
12. Pagination¶
Addresses: SA Audit Finding 9; GitBook Finding 11
12.1 All List Endpoints MUST Paginate¶
Any endpoint that returns a collection of resources MUST support pagination. Never return an unbounded result set.
12.2 Pagination Parameters¶
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
page |
integer | 1 | — | Page number (1-indexed) |
limit |
integer | 20 | 100 | Items per page |
12.3 Pagination Response¶
{
"data": [ ... ],
"pagination": {
"page": 1,
"limit": 20,
"totalCount": 12345,
"totalPages": 618,
"hasNext": true
},
"traceId": "abc-123-def-456"
}
12.4 Cursor-Based Pagination¶
For high-volume endpoints (transaction history, event logs), use cursor-based pagination instead of offset-based. Offset-based pagination degrades at scale (OFFSET 10000 scans 10,000 rows in MySQL).
GET /v1/disbursements/{merchantId}/transactions?cursor=eyJpZCI6MTIzNH0&limit=50
Response includes:
{
"data": [ ... ],
"pagination": {
"limit": 50,
"hasNext": true,
"nextCursor": "eyJpZCI6MTI4NH0"
}
}
Document which endpoints use cursor-based vs offset-based pagination.
13. Rate Limiting¶
Addresses: SA Audit Finding 12; GitBook Finding 12
13.1 Rate Limit Documentation¶
Every endpoint MUST document its rate limit. Rate limits may vary by endpoint type:
| Endpoint Type | Example Limit |
|---|---|
| Transaction initiation | 100 requests/minute |
| Status inquiry | 500 requests/minute |
| Reference data (banks, FX rates) | 1000 requests/minute |
| Bulk operations | 10 requests/minute |
13.2 Rate Limit Response Headers¶
Every API response (not just 429 responses) MUST include these headers:
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit |
Max requests allowed in window | 100 |
X-RateLimit-Remaining |
Requests remaining in current window | 87 |
X-RateLimit-Reset |
Unix timestamp when the window resets | 1711972800 |
13.3 Rate Limit Exceeded Response¶
When a merchant exceeds the rate limit:
-
HTTP status:
429 Too Many Requests -
Retry-Afterheader with seconds until the window resets -
Standard error body with code
RATE_LIMIT_EXCEEDED
13.4 Tiered Limits¶
If different merchant tiers have different rate limits, document the tiers and how merchants can request higher limits.
14. Webhooks & Async Notifications¶
Addresses: SA Audit Finding 4; GitBook Findings 5, 20
14.1 Webhook Requirements¶
Every product that uses asynchronous processing MUST provide a complete webhook specification covering all of the following:
14.2 Event Types¶
Use namespaced event types:
disbursement.initiated
disbursement.completed
disbursement.failed
disbursement.reversed
remittance.initiated
remittance.completed
remittance.failed
remittance.on_hold
remittance.aml_review
payin.initiated
payin.completed
payin.failed
payin.refunded
Document which transaction states trigger webhooks and which do not. If states like stuck, aml_review, or in_review do not trigger webhooks, document that explicitly and recommend a polling interval for merchants to detect these states.
14.3 Webhook Payload¶
{
"eventId": "evt_abc123def456",
"eventType": "disbursement.completed",
"timestamp": "2026-04-01T12:05:00Z",
"data": {
"transactionId": "TXN-2026-04-01-0001",
"reference": "PAY-20260401-001",
"status": "COMPLETED",
"amount": "50000.00",
"currency": "PKR",
"completedAt": "2026-04-01T12:05:00Z"
}
}
14.4 Signature Verification¶
Every webhook request from Simpaisa MUST include a signature header so merchants can verify authenticity:
X-Simpaisa-Signature: t=1711972800,v1=abc123...
Signature format:
signed_payload = timestamp + "." + raw_request_body
signature = HMAC_SHA256(signed_payload, webhook_secret)
Documentation MUST include:
-
Step-by-step verification instructions
-
Code samples in 3+ languages
-
Guidance on timestamp validation (reject webhooks older than 5 minutes to prevent replay attacks)
14.5 Delivery Guarantees¶
| Property | Standard |
|---|---|
| Delivery guarantee | At-least-once |
| Deduplication | Merchants MUST handle duplicate deliveries (use eventId for deduplication) |
| Ordering | Webhooks MAY arrive out of order. Merchants should use timestamp and transaction status, not arrival order |
14.6 Retry Policy¶
| Attempt | Delay | Notes |
|---|---|---|
| 1st retry | 1 minute | After initial delivery failure |
| 2nd retry | 5 minutes | |
| 3rd retry | 30 minutes | |
| 4th retry | 2 hours | |
| 5th retry | 24 hours | Final attempt |
After all retries are exhausted, mark the webhook as failed. Provide a webhook event log in the merchant dashboard where merchants can see delivery status and manually retry.
14.7 Merchant Response Requirements¶
-
Merchant endpoint MUST return HTTP
200within 5 seconds -
Any non-2xx response or timeout triggers retry
-
Merchant endpoint MUST be HTTPS
-
If the merchant endpoint is down for over 24 hours, Simpaisa SHOULD send an alert email to the merchant's technical contact
14.8 Webhook Testing¶
Provide a test endpoint or dashboard feature where merchants can trigger test webhook events to verify their integration.
15. Transaction Lifecycle & State Machines¶
Addresses: SA Audit Finding 20; GitBook Findings 19, 20
15.1 State Machine Documentation¶
Every product MUST document the full transaction state machine including:
-
All possible states with human-readable names (not numeric codes like
0,1) -
All valid state transitions (which state can transition to which)
-
Which API calls trigger which transitions
-
Which states are terminal (no further transitions)
-
Which states trigger webhooks
-
What the merchant should do at each state (wait, retry, contact support)
15.2 State Naming¶
Use UPPER_SNAKE_CASE for state values in API responses:
INITIATED → IN_PROCESS → COMPLETED
→ FAILED → REVERSED
→ ON_HOLD → COMPLETED (after balance top-up)
→ REJECTED
→ STUCK → REJECTED (after max retries)
→ COMPLETED (after manual intervention)
→ AML_REVIEW → COMPLETED
→ REJECTED
Do NOT use numeric states (status = 0, status = 1) in merchant-facing APIs. If the internal system uses numeric states, map them to named states in the API layer.
15.3 SLA Language¶
Document transaction processing as SLAs, not implementation details:
-
Correct: "Disbursements are typically processed within 10 minutes of initiation"
-
Incorrect: "The scheduler runs every five minutes"
Never expose internal scheduler intervals, retry counts, or queue depths to merchants.
16. Field Validation & Data Types¶
Addresses: SA Audit Finding 18; GitBook Finding 21
16.1 Every Field Needs Validation Rules¶
Every request field in the OpenAPI spec MUST include:
| Property | Description | Example |
|---|---|---|
type |
JSON type | string, number, integer, boolean |
required |
Whether the field is required | true / false |
minLength / maxLength |
String length bounds | 1 / 45 |
pattern |
Regex for format validation | ^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$ (IBAN) |
enum |
Allowed values | ["PKR", "USD", "GBP", "BDT"] |
minimum / maximum |
Numeric bounds | 1.00 / 5000000.00 |
format |
Standard format | date-time, email, uri, uuid |
description |
What the field is and how to use it | |
example |
Representative value | "03001234567" |
default |
Default value if omitted (optional fields only) |
16.2 Common Field Standards¶
| Field | Type | Format | Validation | Notes |
|---|---|---|---|---|
amount |
string | Decimal | ^[0-9]+\.[0-9]{2}$, min 0.01 |
String to preserve decimal precision. Always 2 decimal places |
currency |
string | ISO 4217 | ^[A-Z]{3}$, enum of supported currencies |
e.g., PKR, USD, BDT |
msisdn |
string | E.164 | ^\+[1-9]\d{6,14}$ |
Include country code: +923001234567 |
iban |
string | ISO 13616 | ^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$ |
e.g., PK36SCBL0000001123456702 |
reference |
string | — | maxLength 128, ^[a-zA-Z0-9\-_]+$ |
Merchant-provided unique reference |
transactionId |
string | — | Assigned by Simpaisa | Read-only in responses |
merchantId |
string | — | Assigned by Simpaisa | Validated against auth |
email |
string | RFC 5322 | ||
| timestamps | string | date-time | ISO 8601 UTC | 2026-04-01T12:00:00Z |
16.3 Enum Documentation¶
All enum fields MUST list every allowed value with a description. Do not scatter enum values across multiple documentation pages — define them in one place and reference them.
Example: transactionType values must be named, not numeric:
| Value | Description |
|---|---|
WALLET_DEBIT |
Debit from customer wallet |
WALLET_CREDIT |
Credit to customer wallet |
BANK_TRANSFER |
Bank account transfer |
CARD_PAYMENT |
Card payment |
Not: 0, 1, 8, 9.
17. FX Rates & Currency Handling¶
Addresses: SA Audit Finding 17; GitBook Finding 18
17.1 FX Rate Quote Flow¶
The complete FX flow MUST be documented end-to-end:
1. GET /v1/remittances/{merchantId}/fx-rates?from=USD&to=PKR&amount=100.00
→ Returns: rate, convertedAmount, fees, quoteId, expiresAt
2. POST /v1/remittances/{merchantId}/quotes/{quoteId}/confirm
→ Locks the rate. Returns: confirmedQuoteId
3. POST /v1/remittances/{merchantId}/transactions/initiate
Body: { "quoteId": "confirmed-quote-id", ... }
→ Creates the remittance using the locked rate
17.2 Quote Validity¶
-
Document the quote validity window (e.g., 30 seconds, 2 minutes, 5 minutes)
-
The
expiresAttimestamp MUST be included in the quote response -
Using an expired quote MUST return
422with error codeQUOTE_EXPIREDand a message suggesting the merchant request a new quote -
Do NOT silently re-quote at a different rate
17.3 Decimal Precision¶
| Type | Precision | Example |
|---|---|---|
| Transaction amounts | 2 decimal places | 50000.00 |
| FX rates | 6 decimal places | 278.543210 |
| Fee amounts | 2 decimal places | 150.00 |
17.4 Currency Codes¶
Use ISO 4217 three-letter currency codes. Document all supported currencies with their constraints (min/max amounts, supported corridors).
18. OTP & Multi-Factor Authentication Flows¶
Addresses: SA Audit Finding 16; GitBook Findings 16, 17
18.1 OTP Specification¶
| Property | Standard |
|---|---|
| OTP length | 4-6 digits (document per wallet provider) |
| OTP expiry | 5 minutes maximum (not 30 minutes) |
| Max attempts | 3 attempts before lockout |
| Lockout duration | 30 minutes after max attempts exceeded |
| OTP transmission | Request body only, NEVER in URL parameters |
| HTTP status on max attempts | 429 with error code OTP_MAX_ATTEMPTS and Retry-After header |
18.2 Non-OTP (Async Approval) Flows¶
For wallet flows that use push-to-approve (user approves in wallet app):
-
Document the approval window per provider (e.g., Easypaisa: 60s, JazzCash: 360s)
-
Document the polling endpoint and recommended polling interval
-
Document terminal states and what the merchant should do if approval times out
-
Document the webhook events triggered during this flow
18.3 Rate Limiting on OTP Endpoints¶
OTP initiation and verification endpoints MUST have aggressive rate limiting to prevent brute-force attacks:
-
OTP send: max 5 per phone number per hour
-
OTP verify: max 3 attempts per transaction (then lockout)
19. Bulk & Batch Operations¶
Addresses: SA Audit Finding 25
19.1 Batch Endpoint Pattern¶
For high-volume use cases (payroll, mass disbursements), provide batch endpoints:
POST /v1/disbursements/{merchantId}/batches
19.2 Batch Specification¶
| Property | Standard |
|---|---|
| Max batch size | 1,000 items per request |
| Processing | Asynchronous — return 202 Accepted with a batchId |
| Status tracking | GET /v1/disbursements/{merchantId}/batches/{batchId} |
| Item-level status | Each item in the batch has its own status |
| Partial failure | Some items may succeed while others fail |
| Webhooks | batch.completed event when all items are processed |
19.3 Batch Response¶
{
"data": {
"batchId": "BATCH-2026-04-01-001",
"status": "PROCESSING",
"totalItems": 500,
"processedItems": 0,
"succeededItems": 0,
"failedItems": 0,
"createdAt": "2026-04-01T12:00:00Z"
},
"traceId": "abc-123-def-456"
}
20. Reconciliation & Settlement¶
Addresses: GitBook Finding 14
20.1 Transaction Identity¶
Document a clear mapping of transaction identifiers across the lifecycle:
| Identifier | Source | Purpose |
|---|---|---|
reference |
Merchant-provided | Merchant's unique reference for reconciliation |
transactionId |
Simpaisa-generated | Simpaisa's internal transaction identifier |
channelTransactionId |
Payment channel | The ID from Easypaisa, 1Link, etc. |
All three identifiers MUST be returned in the transaction response and included in webhook payloads so merchants can reconcile across systems.
20.2 Settlement Timing¶
Document settlement timing per channel:
| Channel | Settlement |
|---|---|
| Easypaisa | Real-time |
| JazzCash | Real-time |
| 1Link (IBFT) | Real-time |
| 1Link (non-IBFT) | T+1 |
| Bank transfer | T+1 to T+2 |
20.3 Reconciliation API¶
Provide a transaction search/export endpoint for reconciliation:
GET /v1/disbursements/{merchantId}/transactions?dateFrom=2026-04-01&dateTo=2026-04-01&status=COMPLETED&limit=100
Consider supporting CSV export for bulk reconciliation: Accept: text/csv.
20.4 Dispute Handling¶
Document the dispute process: how merchants report missing funds, investigation SLAs, and reversal procedures.
21. Sandbox & Testing¶
Addresses: SA Audit Finding 14; GitBook Finding 22
21.1 Sandbox Environment¶
| Property | Value |
|---|---|
| Base URL | https://sandbox.simpaisa.com |
| Auth | Same mechanism as production, with sandbox-specific credentials |
| Real money | No — sandbox never moves real money |
| Channel behavior | Simulated (does not hit real Easypaisa/JazzCash/1Link) |
| Data persistence | Sandbox data may be reset periodically |
21.2 Test Credentials¶
Provide test merchant credentials in the documentation:
-
Test
merchantId -
Test RSA key pair
-
Test webhook URL configuration
21.3 Test Scenarios (Magic Values)¶
Provide specific test values that simulate deterministic outcomes:
| Test MSISDN / Account | Simulated Outcome |
|---|---|
+923000000001 |
Success — transaction completed |
+923000000002 |
Insufficient balance |
+923000000003 |
Invalid account |
+923000000004 |
Timeout (channel does not respond) |
+923000000005 |
Channel unavailable |
+923000000006 |
Delayed success (webhook arrives after 30s) |
Amount 99999.99 |
Amount exceeds limit |
21.4 Webhook Testing¶
Provide a mechanism (API endpoint or dashboard button) for merchants to trigger test webhook events against their configured callback URL.
22. Transport Security¶
Addresses: SA Audit Finding 22
22.1 TLS Requirements¶
| Requirement | Standard |
|---|---|
| Minimum TLS version | TLS 1.2 |
| Recommended TLS version | TLS 1.3 |
| TLS 1.0 / 1.1 | Not supported. Connections will be rejected |
| Cipher suites | Follow Mozilla "Modern" or "Intermediate" configuration |
22.2 Mutual TLS (mTLS)¶
-
Required for: Pay-Outs, Remittances, Cards (server-to-server)
-
Optional for: Pay-Ins (due to browser-originated flows)
-
Document certificate provisioning, format (PEM), and renewal process
22.3 Certificate Pinning¶
For mobile SDK integrations, provide certificate pinning guidance:
-
Public key pins (not certificate pins, for easier rotation)
-
Backup pins
-
Pin rotation process
23. PII Handling & Data Retention¶
Addresses: SA Audit Finding 23
23.1 PII in API Responses¶
Sensitive fields MUST be masked in API responses where full values are not necessary:
| Field Type | Masking | Example |
|---|---|---|
| Account number | Last 4 digits | ****1234 |
| MSISDN | Last 4 digits | +92300***4567 |
| IBAN | First 4 + last 4 | PK36****6702 |
| Name | Not masked (needed for verification) | Ali Khan |
23.2 PII in Logs¶
-
Full account numbers, IBANs, MSISDNs, and credentials MUST NEVER appear in application logs
-
traceIdshould be used for debugging, not PII -
Log masking must be enforced at the logging framework level, not per-developer discretion
23.3 Data Retention¶
| Data Type | Retention Period | Basis |
|---|---|---|
| Transaction records | 7 years | Regulatory requirement |
| API request/response logs | 90 days | Debugging and dispute resolution |
| Idempotency keys | 48 hours | Operational |
| Webhook delivery logs | 30 days | Debugging |
23.4 Data Deletion¶
Document whether merchants can request deletion or anonymization of PII, and the process for doing so.
24. Health & Status Endpoints¶
Addresses: SA Audit Finding 24
24.1 Health Check¶
GET /health
Returns 200 OK with:
{
"status": "healthy",
"timestamp": "2026-04-01T12:00:00Z"
}
This endpoint does NOT require authentication. It is used by load balancers, monitoring systems, and merchants checking availability.
24.2 Channel Status¶
GET /v1/status/channels
Returns operational status of payment channels:
{
"data": {
"channels": [
{ "name": "easypaisa", "status": "operational" },
{ "name": "jazzcash", "status": "operational" },
{ "name": "1link", "status": "degraded", "message": "Elevated latency" },
{ "name": "ubl", "status": "down", "since": "2026-04-01T11:30:00Z" }
]
},
"timestamp": "2026-04-01T12:00:00Z"
}
24.3 Public Status Page¶
Publish a status page (e.g., status.simpaisa.com) for real-time operational visibility. Include historical uptime and incident reports.
25. Documentation Separation¶
Addresses: SA Audit Finding 3; GitBook cross-cutting
25.1 Two-Tier Documentation¶
| Tier | Audience | Contents | Never Include |
|---|---|---|---|
| Merchant Integration Guide | External merchants | Endpoints, auth, schemas, error codes, webhooks, SDKs, test scenarios | Database tables, queue names, scheduler intervals, cache config, internal architecture |
| Solution Architecture | Internal engineering | Infrastructure, database schema, scheduler design, retry logic, deployment | — |
25.2 Rules¶
-
Merchant-facing documentation MUST NEVER reference: database table names, internal service names, scheduler intervals, cache layers, queue topics, or infrastructure components
-
Replace implementation language with contract language:
-
Incorrect: "Transaction is logged in the
transactiontable withstatus = 0" -
Correct: "Transaction is created with status
INITIATED" -
Incorrect: "The scheduler runs every five minutes to process pending disbursements"
-
Correct: "Disbursements are typically processed within 10 minutes of initiation"
-
26. Naming Conventions¶
Addresses: SA Audit Finding 11; GitBook Findings 8, 10
26.1 Cross-Product Terminology¶
The same concept MUST use the same name across all products:
| Concept | Standard Name | Not |
|---|---|---|
| Start a payment | initiate |
remit-initiate, create, submit |
| Check status | status |
inquire, verify, fetch |
| Get account info | accounts/{id} |
fetch-account |
| Get balance | balance |
balance-data |
| List banks | banks |
banks/list |
| List failure reasons | reasons |
reasons/list |
26.2 URL Naming Rules¶
-
kebab-case for URL paths:
/fx-rates,/fund-transfers,/title-fetch -
camelCase for JSON field names:
transactionId,merchantId -
No camelCase in URLs:
/getFxRate→/fx-rates -
No
/listsuffix: GET on a collection already implies listing -
Resources are plural nouns:
/transactions,/banks,/reasons
26.3 Header Naming¶
-
Standard headers use the HTTP convention:
X-Merchant-Id,X-Timestamp,X-Signature -
Product-specific mode headers (e.g.,
mode: remitance) should be consolidated into path-based product differentiation -
Fix known typos:
remitance→remittance(if the API enforces the misspelled value, accept both during migration)
27. Content Negotiation¶
Addresses: GitBook Finding 25
27.1 Supported Content Types¶
| Direction | Content-Type | Required |
|---|---|---|
| Request | application/json |
Yes |
| Response | application/json |
Yes (default) |
| Response | text/csv |
Optional (reconciliation endpoints) |
27.2 Rules¶
-
JSON is the only required content type
-
Do not advertise
text/plainorapplication/*+jsonin Accept headers unless the API actually supports them -
Requests with unsupported
Content-TypeMUST return415 Unsupported Media Type -
Requests with unsupported
AcceptMUST return406 Not Acceptable
28. Changelog & Deprecation¶
Addresses: SA Audit Finding 21; GitBook Finding 23
28.1 API Changelog¶
Maintain a public API changelog accessible to all merchants. Each entry includes:
-
Date
-
API version affected
-
Type:
added,changed,deprecated,removed,fixed,security -
Description of the change
-
Migration steps (if breaking)
28.2 Merchant Notification¶
| Change Type | Notification | Lead Time |
|---|---|---|
| Breaking change | Email + changelog + Sunset header | 6 months minimum |
| New feature | Changelog + developer portal announcement | Immediate |
| Security fix | Email + changelog | Immediate |
| Deprecation | Email + changelog + Sunset header in responses | 6 months before removal |
28.3 Sunset Header¶
Deprecated API versions MUST include the Sunset response header:
Sunset: Wed, 01 Oct 2026 00:00:00 GMT
29. API Design Checklist¶
Use this checklist when designing or reviewing any Simpaisa API endpoint:
Design & URL¶
- Endpoint uses correct HTTP method (GET for reads, POST for creates, PUT/PATCH for updates, DELETE for deletes)
- URL follows the standard pattern:
/v{version}/{product}/{merchantId}/{resource} - URL uses kebab-case, plural nouns, no
/listsuffix - merchantId in path, validated against authenticated merchant
Authentication & Security¶
- Authentication documented (header names, signature algorithm, code samples)
- TLS 1.2+ required
- PII masked in responses where applicable
- No internal implementation details leaked
Request & Response¶
- Request schema documented (all fields: name, type, required/optional, constraints, regex, min/max, enum)
- Response schema documented for success and all error cases
- HTTP status codes documented per endpoint
- Error response follows standard schema with machine-readable error code
traceIdincluded in every response
Reliability¶
- Idempotency key supported for state-changing operations
- Pagination supported for list endpoints (with
page,limit,totalCount,hasNext) - Rate limits documented with response headers
- Retry guidance documented (which errors are retryable)
Async & Webhooks¶
- Webhook payload documented with all fields
- Webhook signature verification documented
- Webhook retry policy documented
- Transaction state machine documented with webhook triggers
- States that do NOT trigger webhooks are explicitly listed with polling guidance
Developer Experience¶
- Curl example provided (success case and one error case)
- Sandbox test scenario provided (magic values for deterministic testing)
- Field validation rules specified (max length, regex, enum values)
- All enum values documented with descriptions
Operational¶
- API version in URL path
- Deprecation policy followed for breaking changes
- Changelog entry created for any change
30. Testing & Compliance¶
30.1 Layer 1: OpenAPI Spec Linting (CI)¶
-
Run Spectral against all OpenAPI spec files on every PR
-
Custom Spectral ruleset enforcing these standards (idempotency headers, error schema, pagination params, rate limit headers, etc.)
-
Blocks merge if spec violates rules
30.2 Layer 2: Contract Tests¶
For each API product, contract tests verify actual HTTP responses match the OpenAPI spec:
-
Use Schemathesis, Dredd, or Prism to auto-generate test cases from the OpenAPI spec
-
Run against sandbox environments on every deployment
-
Catches behavioral drift between spec and implementation
30.3 Layer 3: Integration Test Suite¶
Key scenarios to cover:
| Scenario | Expected Behavior | Products |
|---|---|---|
| Duplicate idempotency key, same params | Return original response, HTTP 200 | All |
| Duplicate idempotency key, different params | HTTP 409 Conflict | All |
| Invalid auth signature | HTTP 401 with standard error body | All |
| Expired timestamp | HTTP 401 with TIMESTAMP_EXPIRED |
All |
| merchantId mismatch | HTTP 403 with MERCHANT_MISMATCH |
All |
| Exceeded rate limit | HTTP 429 with Retry-After header |
All |
| Pagination: page beyond range | Empty data array, correct pagination metadata | All list endpoints |
| Webhook delivery failure | Retry per policy, then mark failed | All |
| FX rate quote expiry | HTTP 422 with QUOTE_EXPIRED |
Remittances |
| OTP max attempts exceeded | HTTP 429 with lockout duration | Pay-Ins |
| Malformed JSON | HTTP 400 with INVALID_REQUEST |
All |
| Unsupported Content-Type | HTTP 415 | All |
30.4 Compliance Review Cadence¶
-
Every new endpoint: Must pass the API Design Checklist (Section 29) before merge
-
Quarterly: Audit all endpoints against these standards, track compliance percentage
-
Annually: Full re-audit comparable to the initial API Best Practices Audit
Appendix: Audit Findings Cross-Reference¶
This table maps every finding from the API Best Practices Audit (SA Docs) and the GitBook API Documentation Audit to the standard that addresses it.
SA Docs Audit (25 Findings)¶
| # | Finding | Severity | Standard Section |
|---|---|---|---|
| 1 | No idempotency keys | CRITICAL | 11. Idempotency |
| 2 | No standard error format | CRITICAL | 9. Error Handling |
| 3 | Internal details in merchant docs | CRITICAL | 25. Documentation Separation |
| 4 | No webhook specification | CRITICAL | 14. Webhooks & Async Notifications |
| 5 | No authentication flow documentation | CRITICAL | 6. Authentication & Security |
| 6 | No HTTP status codes | CRITICAL | 10. HTTP Status Codes |
| 7 | Inconsistent URL patterns | HIGH | 3. URL Design |
| 8 | POST used for read operations | HIGH | 4. HTTP Methods |
| 9 | No pagination | HIGH | 12. Pagination |
| 10 | No request/response schemas | HIGH | 2. OpenAPI Specification |
| 11 | Naming inconsistencies | HIGH | 26. Naming Conventions |
| 12 | No rate limiting documentation | HIGH | 13. Rate Limiting |
| 13 | Versioning strategy undefined | HIGH | 5. Versioning |
| 14 | No sandbox documentation | HIGH | 21. Sandbox & Testing |
| 15 | Scheduler timing as implicit SLA | MEDIUM | 15. Transaction Lifecycle, 25. Documentation Separation |
| 16 | OTP security hardening gaps | MEDIUM | 18. OTP & MFA Flows |
| 17 | FX rate handling underspecified | MEDIUM | 17. FX Rates & Currency Handling |
| 18 | No field-level validation rules | MEDIUM | 16. Field Validation & Data Types |
| 19 | Payout microservice inconsistency | MEDIUM | 26. Naming Conventions |
| 20 | Missing transaction lifecycle docs | LOW | 15. Transaction Lifecycle |
| 21 | No API changelog/migration guide | LOW | 28. Changelog & Deprecation |
| 22 | No TLS/mTLS requirements documented | MEDIUM | 22. Transport Security |
| 23 | No PII handling/data retention docs | MEDIUM | 23. PII Handling & Data Retention |
| 24 | No health check/status endpoint | LOW | 24. Health & Status Endpoints |
| 25 | No bulk/batch operation support | LOW | 19. Bulk & Batch Operations |
GitBook Audit (28 Findings)¶
| # | Finding | Severity | Standard Section |
|---|---|---|---|
| 1 | Auth differs per product, no unified strategy | CRITICAL | 6. Authentication & Security |
| 2 | Idempotency only on Pay-Ins verify | CRITICAL | 11. Idempotency |
| 3 | No standard error response schema | CRITICAL | 9. Error Handling |
| 4 | No HTTP status codes documented | CRITICAL | 10. HTTP Status Codes |
| 5 | Webhook missing retry policy and signature | CRITICAL | 14. Webhooks & Async Notifications |
| 6 | Pay-Ins response schema incomplete | HIGH | 2. OpenAPI Specification |
| 7 | Pay-Outs missing response schemas | HIGH | 2. OpenAPI Specification |
| 8 | Inconsistent URL patterns | HIGH | 3. URL Design |
| 9 | 7 different base URLs | HIGH | 3. URL Design |
| 10 | Header typo: remitance |
HIGH | 26. Naming Conventions |
| 11 | No pagination for list endpoints | HIGH | 12. Pagination |
| 12 | No rate limiting documentation | HIGH | 13. Rate Limiting |
| 13 | Cards references 7 APIs without full docs | HIGH | 2. OpenAPI Specification |
| 14 | No reconciliation or settlement docs | HIGH | 20. Reconciliation & Settlement |
| 15 | Inconsistent request body wrapping | MEDIUM | 7. Request Design |
| 16 | OTP expiry not documented | MEDIUM | 18. OTP & MFA Flows |
| 17 | Non-OTP flow lacks API details | MEDIUM | 18. OTP & MFA Flows |
| 18 | FX rate quote validity not documented | MEDIUM | 17. FX Rates & Currency Handling |
| 19 | Disbursement states missing transition diagram | MEDIUM | 15. Transaction Lifecycle |
| 20 | Remittance postbacks only for 4 of 9 states | MEDIUM | 14. Webhooks & Async Notifications |
| 21 | Field validation rules incomplete | MEDIUM | 16. Field Validation & Data Types |
| 22 | Sandbox has no test scenarios | MEDIUM | 21. Sandbox & Testing |
| 23 | No API versioning strategy | MEDIUM | 5. Versioning |
| 24 | Typos in page titles | LOW | 26. Naming Conventions |
| 25 | Content negotiation headers unclear | LOW | 27. Content Negotiation |
| 26 | Bangladesh/Nepal/Iraq not assessed | LOW | Scope: apply all sections to every region |
| 27 | Refund API incomplete documentation | HIGH | 2. OpenAPI Specification |
| 28 | 1 Bill and Hosted Page not assessed | LOW | Scope: apply all sections to every product |
This document is version-controlled. Changes require CDO approval and must be announced via the API Changelog.