All PCX services return errors in a consistent JSON envelope:
{
"status": "error",
"message": "Human-readable description of what went wrong"
}
Some services include an additional error field with a technical detail string (present mainly on 500 responses):
{
"status": "error",
"message": "An internal error occurred",
"error": "NullPointerException at use_case line 42"
}
HTTP status codes
| Code | Meaning | When it occurs |
|---|
200 | OK | Successful GET, PUT, PATCH, DELETE |
201 | Created | Successful POST that creates a resource |
204 | No Content | Successful DELETE with no response body |
207 | Multi-Status | Partial success (e.g. target_type: both notifications where one fails) |
400 | Bad Request | Missing required fields, invalid values, validation errors |
401 | Unauthorized | Missing, malformed, or expired auth credentials |
403 | Forbidden | Valid credentials but insufficient role/permission |
404 | Not Found | Resource does not exist |
409 | Conflict | Operation conflicts with current state (e.g. duplicate invite, deletion conflict) |
500 | Internal Server Error | Unhandled exception in the service |
Auth errors in detail
The pcx-authorizer returns 401 in these situations:
X-Api-Key header is absent or set to something other than a valid key or NONE
Authorization header is absent or set to something other than a valid Bearer JWT or NONE
- Both headers are set to
NONE (no credential provided)
- The API key has been revoked or deleted
- The Cognito JWT is expired or invalid
403 is returned by the service layer (not the authorizer) when the identity is valid but the caller’s role does not satisfy the endpoint’s roles constraint.
Successful responses follow one of two shapes depending on the service:
Standard envelope (most services):
{
"status": "success",
"message": "Operation description",
"data": { ... }
}
Direct object (some older endpoints):
{
"user_id": "...",
"email": "...",
...
}
PCX uses DynamoDB cursor-based pagination throughout. There is no offset-based (page=2) pagination.
How it works
- Make your first request — optionally pass
limit to control page size
- If there are more results, the response includes a
last_evaluated_key (or lastEvaluatedKey) field
- Pass that value back as a query parameter on your next request to get the next page
- When
last_evaluated_key is null (or absent), you have reached the last page
Request
GET /v1/users/list?limit=20
GET /v1/users/list?limit=20&last_evaluated_key=<cursor_from_previous_response>
Response
{
"status": "success",
"data": [ ... ],
"count": 20,
"last_evaluated_key": "eyJwa...",
"meta": {
"last_evaluated_key": "eyJwa..."
}
}
DynamoDB pagination cursors are opaque tokens. Do not attempt to parse, modify, or construct them manually — they are base64-encoded DynamoDB key objects and will cause a 400 if malformed.
Key naming inconsistency
Different services use slightly different field names for the pagination cursor — this is a known inconsistency:
| Service | Request param | Response field |
|---|
| Organizations | lastEvaluatedKey | lastEvaluatedKey |
| Users | last_evaluated_key | last_evaluated_key |
| Virtual Accounts | last_evaluated_key | last_evaluated_key |
| Beneficiary | last_evaluated_key | last_evaluated_key |
| Transaction | last_evaluated_key | last_evaluated_key |
Decimal handling
Monetary values are stored as DynamoDB Decimal types and serialized to JSON as floats. Always treat amounts as number in your client — never as integers.
Idempotency
PCX does not currently implement idempotency keys. For critical operations (payment creation, KYB initiation), implement idempotency in your own layer by checking for the existence of a resource before creating it.