OAuth 2.0
Server-to-server calls to the Credicorp partner API use the OAuth 2.0 client-credentials grant. The authorization server is hub.credicorp.co.uk; tokens are short-lived EdDSA (Ed25519) JWTs minted against the /partner/v1 ring.
Client-credentials only. The partner OAuth plane supports the client-credentials grant exclusively — no authorization-code or refresh-token flows. The issuer is hub.credicorp.co.uk; every access token is signed with EdDSA (Ed25519), key id hub-oauth-ed25519.
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 with mcp.read alone cannot call account-read tools. The token endpoint accepts application/x-www-form-urlencoded or HTTP Basic for the credentials.
curl -s https://hub.credicorp.co.uk/partner/v1/oauth/token \ -d "grant_type=client_credentials" \ -d "client_id=$CC_CLIENT_ID" \ -d "client_secret=$CC_CLIENT_SECRET" \ -d "scope=mcp.read account.read"
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: ["mcp.read", "account.read"], }); 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' => ['mcp.read', 'account.read'], ]); $token = $cc->auth()->accessToken();
Request parameters
| Field | Type | Description | |
|---|---|---|---|
grant_type | string | req | Always client_credentials. |
client_id | string | req | Client identifier provisioned by the Credicorp operator and provided out-of-band when your client is registered. |
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. |
Token response
# → 200 OK { "access_token": "eyJhbGciOiJFZERTQSIsImtpZCI6Imh1Yi1vYXV0aC1lZDI1NTE5In0…", "token_type": "Bearer", "expires_in": 300, "scope": "mcp.read account.read" }
The access token is a signed EdDSA JWT (Ed25519, key id hub-oauth-ed25519). 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
The partner scope vocabulary is a closed, read-only set. 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 | Tier | Grants |
|---|---|---|
mcp.read | Floor | List and initialise the authenticated MCP server; decisioning-explanation tools. Required for any authed MCP session. |
account.read | Staff | PII reads: application status, customer summary, loan status, loan statement, behavioural digest. Every call is audit-logged. |
ops.read | Staff | Operational aggregate reads: ops queue status, enquiry list. Non-PII. |
owner.read | Owner | Platform metrics, ops metrics, portfolio overview, config snapshot. Strongest standing partner grant. |
Least privilege. The MCP server filters its tool list to the scopes the bearer was granted — a token minted with only mcp.read will never see account.read tools. Request only what your integration needs; owner.read should be reserved for dashboard-style applications.
Token caching
Access tokens are valid for five minutes (expires_in: 300). Cache and reuse them. Minting a fresh token on every API call is the most common integration mistake — it multiplies your request volume and trips the token-endpoint rate cap (10 req/min per IP).
- Store the token in process memory, or in Redis / Memcached if you run multiple instances, keyed by
client_id + scope-set. - Refresh proactively at ~80% of lifetime (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://hub.credicorp.co.uk/partner/v1/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: "mcp.read account.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
Client secrets are provisioned out-of-band by the Credicorp operator — there is no self-service endpoint. The operator supports two live secrets per client so you can rotate with zero downtime. Email developers@credicorp.co.uk to request a rotation.
- Request a second secret from the Credicorp developer team. Once issued, both old and new authenticate simultaneously.
- Deploy the new secret to your environment — rolling restart, blue/green, whatever you run. Old instances keep working on the old secret.
- Confirm that all instances are minting tokens with the new secret, then ask the developer team to revoke the old one.
- Old secret revoked. Any token already issued under it stays valid until it expires naturally; no new tokens can be minted with it after revocation.
If a secret leaks, contact the developer team immediately — don't wait for an orderly roll. Revocation is instant once actioned. Email developers@credicorp.co.uk with the subject line Urgent: secret leak. A fresh secret will be issued and the compromised one revoked.
Authorization-server discovery
The authorization server publishes an RFC 8414 metadata document. Consume it at start-up — never hard-code endpoint URLs, so your integration picks up key rotations automatically:
{
"issuer": "https://hub.credicorp.co.uk",
"token_endpoint": "https://hub.credicorp.co.uk/partner/v1/oauth/token",
"jwks_uri": "https://hub.credicorp.co.uk/partner/v1/oauth/jwks",
"introspection_endpoint": "https://hub.credicorp.co.uk/partner/v1/oauth/introspect",
"grant_types_supported": ["client_credentials"],
"response_types_supported": [],
"scopes_supported": ["mcp.read", "account.read", "ops.read", "owner.read"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
"id_token_signing_alg_values_supported": ["EdDSA"],
"service_documentation": "https://hub.credicorp.co.uk/partner/v1/auth.md"
}Verify access tokens against jwks_uri (key type OKP / curve Ed25519, key id hub-oauth-ed25519). Check iss, aud, exp and scope. There is no userinfo_endpoint on the partner plane.
Authorization-server endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /partner/v1/oauth/token | Issue an access token (client-credentials grant). |
| POST | /partner/v1/oauth/introspect | Inspect a token's active state, scope and expiry. |
| GET | /partner/v1/oauth/jwks | EdDSA public signing key (JWKS) for token verification. |
| GET | /.well-known/oauth-authorization-server | RFC 8414 authorization-server metadata discovery. |
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 roadmap for publishable keys and webhook secrets, and Request signing roadmap to verify webhook deliveries.
