Errors
Every error from the Credicorp API uses one envelope and a conventional HTTP status. Read the machine-readable code to branch your logic and the message for logs — never parse the prose. Quote the request_id when you contact support.
The API uses standard HTTP status codes: 2xx for success, 4xx when something about the request is wrong (and retrying unchanged won't help), and 5xx for the rare problem on our side. Failures relating to lending outcomes — a declined decision, a returned payment — are not API errors: those succeed with 2xx and carry the outcome in the resource body.
The error envelope
All errors share this shape. The top-level key is always error.
{
"error": {
"type": "invalid_request_error",
"code": "parameter_missing",
"message": "Missing required parameter: amount_pence.",
"param": "amount_pence",
"doc_url": "https://dev.credicorp.co.uk/api-reference/apply.html#create",
"request_id": "req_7Hs2bV9k"
}
}| Field | Type | Description | |
|---|---|---|---|
type | string | always | Broad category — one of the five types below. Branch on this first. |
code | string | always | Stable, machine-readable identifier for the specific error. Your code should switch on this. |
message | string | always | Human-readable explanation for logs. Wording may change — never parse it. |
param | string | opt | The offending field, for validation errors. Dotted path, e.g. business.company_number. |
doc_url | string | opt | Deep link to the relevant documentation section. |
request_id | string | always | Unique ID for the request. Also returned in the Credicorp-Request-Id response header. Quote it to support. |
The request_id is on every response, success or failure, via the Credicorp-Request-Id header. Log it alongside your own correlation ID so a single line in your logs maps to a single line in ours.
Error types
The type tells you, at a glance, whose problem it is and whether a retry could ever help.
| type | HTTP | Meaning |
|---|---|---|
authentication_error | 401 | The token is missing, malformed, expired or revoked. |
permission_error | 403 | Authenticated, but the key lacks the required scope for this resource. |
invalid_request_error | 400/404/409 | The request is malformed, references something that doesn't exist, or conflicts with current state. |
rate_limit_error | 429 | Too many requests. Back off and retry — see Rate limits. |
api_error | 500/503 | Something went wrong on our end. Safe to retry idempotent requests with back-off. |
HTTP status map
The full set of statuses the API returns and how your client should treat each.
| Status | When | Retry? |
|---|---|---|
| 200 OK | Request succeeded. | — |
| 201 Created | A resource was created (e.g. an application). | — |
| 202 Accepted | Accepted for async processing (e.g. a disbursement queued). | — |
| 400 Bad Request | Malformed JSON or a failed validation rule. | No — fix the request. |
| 401 Unauthorized | No valid credential. | No — refresh the token. |
| 403 Forbidden | Valid credential, insufficient scope. | No — request the scope. |
| 404 Not Found | Unknown ID, or hidden by your permissions. | No. |
| 409 Conflict | State conflict, or an Idempotency-Key reused with a different body. | No — reconcile first. |
| 422 Unprocessable | Well-formed but semantically invalid (e.g. term outside 3–60 months). | No — correct the value. |
| 429 Too Many Requests | Rate limit exceeded. Honour Retry-After. | Yes — with back-off. |
| 500 Server Error | An unexpected fault on our side. | Yes — idempotent only. |
| 503 Unavailable | Temporary maintenance or overload. | Yes — back-off; check status. |
Common error codes
Switch on code for predictable handling. These are stable identifiers — new codes may be added, so treat an unknown code as a generic failure of its type.
| code | type | What it means |
|---|---|---|
parameter_missing | invalid_request_error | A required field was omitted; see param. |
parameter_invalid | invalid_request_error | A field is the wrong type or fails a format rule. |
resource_missing | invalid_request_error | No object exists for the supplied ID. |
idempotency_conflict | invalid_request_error | An Idempotency-Key was reused with a different payload. |
company_not_found | invalid_request_error | The Companies House number did not resolve to an active entity. |
amount_below_minimum | invalid_request_error | Requested principal is under the £1,000 floor. |
term_out_of_range | invalid_request_error | term_months is outside the 3–60 month range. |
mandate_not_active | invalid_request_error | A payment was attempted against a revoked or pending mandate. |
token_expired | authentication_error | The bearer token has passed its expiry; refresh it. |
token_revoked | authentication_error | The credential was revoked in the dashboard. |
insufficient_scope | permission_error | The key is not granted the scope this endpoint needs. |
rate_limit_exceeded | rate_limit_error | Request rate over the limit; see Retry-After. |
internal_error | api_error | An unexpected fault. Retry idempotent calls; then contact support with the request_id. |
A validation error in full
Sending a request that omits a required field returns 400 with param populated:
curl -i https://api.credicorp.co.uk/partner/v1/applications \ -H "Authorization: Bearer $TOKEN" \ -d '{ "term_months": 12 }' HTTP/2 400 Credicorp-Request-Id: req_7Hs2bV9k Content-Type: application/json { "error": { "type": "invalid_request_error", "code": "parameter_missing", "message": "Missing required parameter: amount_pence.", "param": "amount_pence", "request_id": "req_7Hs2bV9k" } }
Handling errors in the SDKs
The SDKs raise typed exceptions that mirror the type field, each exposing code, param and requestId. Catch the specific exception you can recover from and let the rest bubble.
use Credicorp\Exception\{InvalidRequestException, RateLimitException, ApiErrorException}; try { $app = $cc->applications->create($payload); } catch (InvalidRequestException $e) { // $e->getCode() === 'parameter_missing', $e->getParam() === 'amount_pence' log_warning($e->getMessage(), $e->getRequestId()); } catch (RateLimitException $e) { sleep($e->getRetryAfter()); // then retry } catch (ApiErrorException $e) { // transient — safe to retry idempotent calls with back-off }
try { const app = await cc.applications.create(payload); } catch (err) { switch (err.type) { case 'invalid_request_error': console.warn(err.code, err.param, err.requestId); break; case 'rate_limit_error': await sleep(err.retryAfter * 1000); // retry break; default: throw err; } }
import credicorp try: app = cc.applications.create(**payload) except credicorp.error.InvalidRequestError as e: log.warning("%s on %s (%s)", e.code, e.param, e.request_id) except credicorp.error.RateLimitError as e: time.sleep(e.retry_after) # then retry except credicorp.error.APIError: pass # transient — retry idempotent calls with back-off
Retry only idempotent requests on 5xx and 429, and always send the same Idempotency-Key so a retried POST can't double-create. See the idempotency guide.
