Authentication

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.

POSThttps://hub.credicorp.co.uk/partner/v1/oauth/token
bash
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"
javascript
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();
php
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

FieldTypeDescription
grant_typestringreqAlways client_credentials.
client_idstringreqClient identifier provisioned by the Credicorp operator and provided out-of-band when your client is registered.
client_secretstringreqProject secret. Server-side only — never ship it to a browser or mobile app.
scopestringoptSpace-separated scopes. Defaults to the project's full granted set; narrow it to follow least-privilege.

Token response

json
# → 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.

ScopeTierGrants
mcp.readFloorList and initialise the authenticated MCP server; decisioning-explanation tools. Required for any authed MCP session.
account.readStaffPII reads: application status, customer summary, loan status, loan statement, behavioural digest. Every call is audit-logged.
ops.readStaffOperational aggregate reads: ops queue status, enquiry list. Non-PII.
owner.readOwnerPlatform 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.
javascript
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.

  1. Request a second secret from the Credicorp developer team. Once issued, both old and new authenticate simultaneously.
  2. Deploy the new secret to your environment — rolling restart, blue/green, whatever you run. Old instances keep working on the old secret.
  3. Confirm that all instances are minting tokens with the new secret, then ask the developer team to revoke the old one.
  4. 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:

GEThttps://hub.credicorp.co.uk/.well-known/oauth-authorization-server
json
{
  "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

MethodEndpointPurpose
POST/partner/v1/oauth/tokenIssue an access token (client-credentials grant).
POST/partner/v1/oauth/introspectInspect a token's active state, scope and expiry.
GET/partner/v1/oauth/jwksEdDSA public signing key (JWKS) for token verification.
GET/.well-known/oauth-authorization-serverRFC 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.

StatusErrorCause
400invalid_requestMissing or malformed grant_type / parameters.
401invalid_clientUnknown client_id or wrong client_secret. Check you're not mixing sandbox and live.
400invalid_scopeRequested a scope the project isn't granted.
403insufficient_scopeReturned by the API when a call needs a scope your token lacks.
503temporarily_unavailableAuth 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.