Decisioning API
Credicorp's decision engine is AI-led and authoritative. When an application is submitted it returns an outcome — approved, referred or declined — with indicative pricing and machine-readable reason codes, usually within a few seconds.
Roadmap — not yet available. The REST endpoints below do not exist yet and return 503 Service Unavailable. Decision data is available today via the decisioning_explanation MCP tool (live, mcp.read scope). The REST surface documented here is planned; availability will be announced on the changelog.
A decision is read-only: you do not create it, the engine produces exactly one per application. Read it via the MCP tool today or, once available, via the REST endpoints below (a read grant will be required). The most reliable way to consume an outcome is the decision.completed webhook; the endpoints below let you fetch or reconcile on demand.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /applications/{id}/decision | The decision for one application. |
| GET | /decisions/{id} | Retrieve a decision by its own id. |
| GET | /decisions | List & filter decisions. |
Retrieve a decision
Returns the decision attached to an application. Before the engine resolves, this returns 404 with code not_found — prefer the webhook over polling.
curl https://hub.credicorp.co.uk/partner/v1/applications/app_8Kd2c9Qm/decision \ -H "Authorization: Bearer $TOKEN"
$decision = $cc->applications->decision('app_8Kd2c9Qm'); if ($decision->outcome === 'approved') { $apr = $decision->apr; // e.g. 14.9 }
const decision = await cc.applications.decision('app_8Kd2c9Qm'); if (decision.outcome === 'approved') { const { apr, offer } = decision; // indicative pricing }
Response 200 OK
{
"id": "dec_5Tg9Hb2P",
"object": "decision",
"application_id": "app_8Kd2c9Qm",
"outcome": "approved",
"offer": {
"amount_pence": 2500000,
"term_months": 12,
"apr": 14.9,
"monthly_repayment_pence": 224170,
"total_repayable_pence": 2690040,
"is_indicative": true
},
"reasons": [
{ "code": "affordability_ok", "category": "affordability" },
{ "code": "credit_ok", "category": "credit" }
],
"expires_at": "2026-07-13T10:00:03Z",
"decided_at": "2026-06-29T10:00:03Z"
}Response fields
| Field | Type | Description |
|---|---|---|
outcome | enum | approved, referred or declined. See Outcomes. |
offer | object | null | Present when approved. Null for referred / declined. |
offer.apr | number | Indicative representative APR as a percentage, e.g. 14.9. |
offer.is_indicative | boolean | true until the credit agreement is signed; final terms are confirmed at acceptance. |
reasons[] | array | Structured reason codes — always populated, including on approval. |
expires_at | string | When an approved offer lapses if not accepted (RFC 3339). |
decided_at | string | When the engine resolved the outcome. |
Outcomes
| Outcome | Meaning & next step |
|---|---|
| approved | An offer is available at the returned offer.amount_pence / apr. Present it, then take acceptance — on signature the application moves toward funded. |
| referred | The engine deferred to a human underwriter. No action needed; it resolves to approved or declined later and fires a second decision.completed event. Typically within one business day. |
| declined | No offer. Inspect reasons to explain the outcome to the applicant. A new application may be made after a cooling-off period. |
Indicative APR & offer
The apr on an approved decision is indicative until the customer accepts and signs the credit agreement. It is derived from the modelled risk of the entity, the requested amount_pence and term_months, and current pricing. Re-pricing can occur if material new information surfaces at acceptance (for example a fresh adverse filing), in which case offer.is_indicative stays true and the final figures arrive on the funded account. Always render the offer object verbatim rather than recomputing repayments yourself — the engine accounts for fees and rounding to the penny.
Reason codes
Every decision carries one or more reasons. Each has a stable code and a category so you can group them in your UI. Treat the set as additive — new codes may appear within v1; branch on the ones you recognise and fall back to the human-readable message otherwise.
| Code | Category | Meaning |
|---|---|---|
affordability_ok | affordability | Modelled serviceability supports the request. |
affordability_low | affordability | Serviceability below threshold for the requested amount. |
credit_ok | credit | Credit profile within appetite. |
credit_adverse | credit | Adverse markers (CCJ, default) outside appetite. |
thin_file | credit | Insufficient trading history to model reliably. |
banking_data_insufficient | affordability | Open-banking coverage too short to assess cashflow. |
director_risk | identity | Officer screening raised a flag requiring review. |
ineligible_entity | eligibility | Not a UK incorporated company or LLP. |
amount_exceeds_limit | eligibility | Request above the entity's modelled exposure ceiling. |
manual_review_required | policy | Policy routed the case to an underwriter (drives referred). |
List decisions
Cursor-paginated, newest first. Filter by outcome and decided_after for reconciliation or analytics.
curl "https://hub.credicorp.co.uk/partner/v1/decisions?outcome=approved&limit=50" \ -H "Authorization: Bearer $TOKEN"
Don't poll in a tight loop. Subscribe to decision.completed; the webhook carries the full decision body and fires the instant the engine resolves — including the second time, when a referred case is settled by an underwriter.
The decision is authoritative: there is no partner override to flip an outcome, and money only ever moves through the Payments roadmap resource after an offer is accepted. Test every branch deterministically with the scenario companies in Sandbox & test data.
Errors
All errors from this endpoint use the standard hub error envelope. Branch on code for precise handling; quote correlation_id when contacting support.
{
"error": {
"code": "not_found",
"message": "The requested resource was not found.",
"correlation_id": "cor_01J2K3M4N5P6Q7R8S9T0A1V2W3",
"retryable": false
}
}| code | HTTP | When |
|---|---|---|
not_found | 404 | No decision exists for the supplied ID, or it is not visible to this client. |
decision_locked | 423 | The decision is locked — only an owner-sudo release can move it. |
unauthenticated | 401 | Missing or invalid access token. |
token_expired | 401 | Token has expired — refresh it and retry. |
insufficient_scope | 403 | Token not granted the scope this endpoint requires. |
rate_limited | 429 | Request rate over the quota; honour retry_after_ms and Retry-After. See Rate limits. |
internal_error | 500 | Unexpected fault on our side. Safe to retry idempotent calls with back-off; quote correlation_id to support. |
service_unavailable | 503 | Temporary maintenance or overload. Retry with back-off; check status. |
The full error code list and all envelope fields are on the Errors page.
