Authentication

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.

POSThttps://api.credicorp.co.uk/oauth/token
bash
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"
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: ["applications:write", "decisions:read", "payments:write"],
});

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'        => ['applications:write', 'decisions:read', 'payments:write'],
]);

$token = $cc->auth()->accessToken();

Request parameters

FieldTypeDescription
grant_typestringreqAlways client_credentials.
client_idstringreqProject client identifier. Begins cc_client_.
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.
audiencestringoptTarget API. Defaults to partner/v1. Set internal/v1 only if your project is provisioned for it.

Token response

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

ScopeRingGrants
applications:writeApplyCreate & update business-loan applications; submit for decision.
applications:readApplyRead applications, their status and the hosted hand-off URL.
decisions:readDecisioningRead AI decision outcomes, offered terms and the reason codes.
payments:writePaymentsCreate PISP payment links, collection schedules & disbursement instructions.
payments:readPaymentsRead payment, mandate & reconciliation state.
identity:readIdentityRead KYB/KYC verification results and director claims.
accounts:readAccountsRead funded loan accounts, balances & servicing events.
accounts:writeAccountsRaise settlement quotes & servicing requests on a funded account.
webhooks:managePlatformRegister, rotate & delete webhook endpoints and their signing secrets.
partner:managePartnerManage introductions, payouts, sub-accounts & white-label settings.
openid profile emailOIDCStandard 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.
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://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.

  1. Generate a second secret from the developer dashboard (or POST /oauth/clients/{id}/secrets). Both old and new now authenticate.
  2. Deploy the new secret to your environment — rolling restart, blue/green, whatever you run. Old instances keep working on the old secret.
  3. Verify traffic is minting tokens with the new secret (the dashboard shows last-used timestamps per secret).
  4. 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:

GEThttps://sso.credicorp.co.uk/.well-known/openid-configuration
json
{
  "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

MethodEndpointPurpose
POST/oauth/tokenIssue an access token (client-credentials or authorization-code).
GET/oauth/authorizeBegin the OIDC authorization-code flow (browser redirect).
POST/oauth/revokeRevoke an access or refresh token immediately.
POST/oauth/introspectInspect a token's active state, scope and expiry.
GET/.well-known/openid-configurationOIDC discovery document.
GET/.well-known/jwks.jsonPublic 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.

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 for publishable keys and webhook secrets, and Request signing to verify webhook deliveries.