11. Error handling for Portal consumers
Portal errors use the envelope below. Generic codes include BAD_REQUEST (400), UNAUTHENTICATED (401), FORBIDDEN (403), NOT_FOUND (404), METHOD_NOT_ALLOWED (405), CONFLICT (409), GONE (410), VALIDATION_ERROR (422), RATE_LIMITED (429), SERVICE_UNAVAILABLE (503) and SERVER_ERROR (other failures). Authentication/context codes are listed in section 3.5. Field errors are sanitized; 5xx responses hide internal diagnostics.
Implemented Portal endpoints return stable error_code values and field errors when applicable:
{
"success": false,
"error_code": "VALIDATION_ERROR",
"message": "Validation error",
"errors": { "environment": ["The environment must be test or live."] }
}
| HTTP | Consumer handling |
|---|---|
| 400 | Malformed request |
| 401 | Invalid/expired user authentication on a protected call; clear the Portal session and require login again |
| 403 | Known authenticated actor lacks permission; show a permission error |
| 404 | Resource absent or outside authorized merchant/environment; do not disclose cross-merchant existence |
| 409 | Membership, invitation, or lifecycle state conflict; use the returned error code |
| 410 | Invitation or credential delivery expired/consumed/revoked |
| 422 | Validation failed; display field errors |
| 429 | Throttled; honor retry delay |
| 500/503 | Server failure or unavailable service; show a retryable failure without internal details |
Treat HTTP status as authoritative and error_code as the machine-readable distinction, not the text of message. On 422, bind errors to form fields and preserve the user's editable state; OTP values must remain strings so leading zeroes survive. Public password-reset and login routes can return their own envelopes without the Portal error_code; parse them according to section 3 and use a generic fallback. Do not show application-authentication/provisioning errors as wrong user passwords. Server errors may hide their real operational cause, so correlate them through the DLT team's server diagnostics rather than expecting a raw exception in the response.
Important state conflicts to handle explicitly: LAST_ADMINISTRATOR prevents a last-admin role/removal action; MEMBER_ALREADY_EXISTS, INVITATION_ALREADY_PENDING, USER_BELONGS_TO_ANOTHER_MERCHANT, USER_NOT_ELIGIBLE, and USER_IDENTITY_CONFLICT prevent conflicting invitation/membership changes; INVITATION_UNAVAILABLE means obtain a new invitation instead of retrying the same proof. Generic credential CONFLICT/GONE states require reading metadata and, if appropriate, issuing replacement credentials. Never treat an omitted/unavailable history body as an instruction to replay its original payment request.
Do not automatically retry mutations that create invitations, issue/rotate credentials, or consume one-use deliveries after an ambiguous network timeout. Read current state first. Read-only collection requests can be retried with bounded backoff. Existing APIs do not all include error_code; preserve fallback support for their message/errors shapes.