OAuth 2.0
Server-to-server calls to the Credicorp business-lending API use the OAuth 2.0 client-credentials grant. End-user sign-in — “Sign in with Credicorp” — uses OpenID Connect on top of the same authorization server.
Two flows, one authorization server. Your backend authenticates with client-credentials to call the API on its own behalf. To identify a director or signatory in your own UI, use the OpenID Connect authorization-code flow. Both are issued by sso.credicorp.co.uk.
Client-credentials grant
Exchange your project's client_id and client_secret for a short-lived bearer token. Request only the scopes the call needs — a token minted for applications:write cannot read payments. The token endpoint accepts application/x-www-form-urlencoded or HTTP Basic for the credentials.
curl -s https://api.credicorp.co.uk/oauth/token \ -d "grant_type=client_credentials" \ -d "client_id=$CC_CLIENT_ID" \ -d "client_secret=$CC_CLIENT_SECRET" \ -d "scope=applications:write decisions:read payments:write"
import { Credicorp } from "@credicorp/sdk"; // The SDK fetches, caches and refreshes the token for you. const cc = new Credicorp({ clientId: process.env.CC_CLIENT_ID, clientSecret: process.env.CC_CLIENT_SECRET, scopes: ["applications:write", "decisions:read", "payments:write"], }); const token = await cc.auth.accessToken();
use Credicorp\Client; // Token acquisition and refresh are handled by the client. $cc = new Client([ 'client_id' => getenv('CC_CLIENT_ID'), 'client_secret' => getenv('CC_CLIENT_SECRET'), 'scopes' => ['applications:write', 'decisions:read', 'payments:write'], ]); $token = $cc->auth()->accessToken();
Request parameters
| Field | Type | Description | |
|---|---|---|---|
grant_type | string | req | Always client_credentials. |
client_id | string | req | Project client identifier. Begins cc_client_. |
client_secret | string | req | Project secret. Server-side only — never ship it to a browser or mobile app. |
scope | string | opt | Space-separated scopes. Defaults to the project's full granted set; narrow it to follow least-privilege. |
audience | string | opt | Target API. Defaults to partner/v1. Set internal/v1 only if your project is provisioned for it. |
Token response
# → 200 OK { "access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImNjLTA3In0…", "token_type": "Bearer", "expires_in": 3600, "scope": "applications:write decisions:read payments:write" }
The access token is a signed ES256 JWT. You don't need to parse it — treat it as an opaque bearer string — but if you do, the public keys are published at the JWKS endpoint below so you can verify iss, aud, exp and scope locally.
Scope catalogue
Scopes map one-to-one onto the platform's resource rings. A request that touches a resource outside its token's scope is rejected with 403 insufficient_scope and a WWW-Authenticate header naming the scope required.
| Scope | Ring | Grants |
|---|---|---|
applications:write | Apply | Create & update business-loan applications; submit for decision. |
applications:read | Apply | Read applications, their status and the hosted hand-off URL. |
decisions:read | Decisioning | Read AI decision outcomes, offered terms and the reason codes. |
payments:write | Payments | Create PISP payment links, collection schedules & disbursement instructions. |
payments:read | Payments | Read payment, mandate & reconciliation state. |
identity:read | Identity | Read KYB/KYC verification results and director claims. |
accounts:read | Accounts | Read funded loan accounts, balances & servicing events. |
accounts:write | Accounts | Raise settlement quotes & servicing requests on a funded account. |
webhooks:manage | Platform | Register, rotate & delete webhook endpoints and their signing secrets. |
partner:manage | Partner | Manage introductions, payouts, sub-accounts & white-label settings. |
openid profile email | OIDC | Standard OpenID Connect scopes for “Sign in with Credicorp”. |
Least privilege. A background worker that only reads decisions should request decisions:read alone. Don't mint a token with payments:write for a read-only job — if that token leaks, the blast radius is everything it was scoped for.
Token caching
Access tokens are valid for one hour (expires_in: 3600). Cache and reuse them. Minting a fresh token on every API call is the most common integration mistake — it doubles your request volume and will trip the 10 req/s limit on the token endpoint.
- Store the token in process memory, or in Redis / Memcached if you run multiple instances, keyed by
client_id + scope-set. - Refresh proactively at ~90% of lifetime (roughly 60 seconds before
exp) so an in-flight request never races expiry. - On a 401
invalid_token, refresh once and retry the original request a single time.
let cached = { token: null, exp: 0 }; async function getToken() { const now = Date.now() / 1000; if (cached.token && now < cached.exp - 60) return cached.token; // reuse const r = await fetch("https://api.credicorp.co.uk/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: process.env.CC_CLIENT_ID, client_secret: process.env.CC_CLIENT_SECRET, scope: "applications:write decisions:read", }), }); const j = await r.json(); cached = { token: j.access_token, exp: now + j.expires_in }; return cached.token; }
The official SDKs do all of this for you — in-memory caching, proactive refresh and one-shot retry on 401. Reach for raw token handling only if you can't use an SDK.
Rotating client secrets
The authorization server supports two live secrets per client so you can rotate with zero downtime. The roll never requires a maintenance window.
- Generate a second secret from the developer dashboard (or
POST /oauth/clients/{id}/secrets). Both old and new now authenticate. - Deploy the new secret to your environment — rolling restart, blue/green, whatever you run. Old instances keep working on the old secret.
- Verify traffic is minting tokens with the new secret (the dashboard shows last-used timestamps per secret).
- Revoke the old secret. Any token already issued under it stays valid until it expires; no new tokens can be minted with it.
If a secret leaks, revoke immediately — don't wait for the orderly roll. Revocation is instant. Then issue a fresh secret and redeploy. You can also revoke all outstanding access tokens for a client from the dashboard, which forces every cache to re-mint.
OpenID Connect — Sign in with Credicorp
To identify a director or authorised signatory inside your own product, use the OIDC authorization-code flow with PKCE on the public/v1 ring. Start from the discovery document — never hard-code endpoints, since rotation and new endpoints are published there:
{
"issuer": "https://sso.credicorp.co.uk",
"authorization_endpoint": "https://sso.credicorp.co.uk/oauth/authorize",
"token_endpoint": "https://api.credicorp.co.uk/oauth/token",
"userinfo_endpoint": "https://api.credicorp.co.uk/public/v1/userinfo",
"jwks_uri": "https://sso.credicorp.co.uk/.well-known/jwks.json",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"],
"scopes_supported": ["openid", "profile", "email", "applications:read"],
"id_token_signing_alg_values_supported": ["ES256"],
"code_challenge_methods_supported": ["S256"]
}The flow returns an id_token (an ES256 JWT) whose claims identify the signed-in person and, where consented, the companies they are a director of. Verify the signature against jwks_uri and check iss, aud, exp and nonce. The claim shape, the consent screen and the userinfo endpoint are documented in the Identity reference.
Authorization-server endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /oauth/token | Issue an access token (client-credentials or authorization-code). |
| GET | /oauth/authorize | Begin the OIDC authorization-code flow (browser redirect). |
| POST | /oauth/revoke | Revoke an access or refresh token immediately. |
| POST | /oauth/introspect | Inspect a token's active state, scope and expiry. |
| GET | /.well-known/openid-configuration | OIDC discovery document. |
| GET | /.well-known/jwks.json | Public signing keys (JWKS) for token verification. |
Token errors
The token endpoint returns standard OAuth 2.0 errors. Treat invalid_client as a hard failure — do not retry with the same credentials.
| Status | Error | Cause |
|---|---|---|
| 400 | invalid_request | Missing or malformed grant_type / parameters. |
| 401 | invalid_client | Unknown client_id or wrong client_secret. Check you're not mixing sandbox and live. |
| 400 | invalid_scope | Requested a scope the project isn't granted. |
| 403 | insufficient_scope | Returned by the API when a call needs a scope your token lacks. |
| 503 | temporarily_unavailable | Auth server briefly unavailable — back off and retry. |
Most integrations only need client-credentials. If you're calling from a browser, mobile app or webhook receiver instead of your own backend, read API keys for publishable keys and webhook secrets, and Request signing to verify webhook deliveries.
