DEVELOPER HUBv1.0.0 Specification

Frica ID Integration Handbook

Authoritative guides for single sign-on (OAuth 2.1 / OIDC), scoped machine-to-machine API keys, real-time webhooks, pre-signed cloud storage, and subscription entitlements.

api.frica.id: CHECKING
Developer Console
Architecture

1. Platform Architecture & Standards

Frica ID is an enterprise-grade digital identity, single sign-on (SSO), and developer platform designed specifically for the Pan-African and global ecosystem. It adheres to strict modern internet standards:

OAuth 2.1 & OIDC 1.0

Enforces mandatory PKCE (RFC 7636) and Refresh Token Rotation (RTR). Deprecates legacy implicit and password grants.

Cryptographic Hardening

RS256 asymmetric token signatures, Argon2id (RFC 9106) password hashing, and AES-256-GCM envelope encryption for secrets at rest.

Dual Multi-Provider Billing

Native support for Safaricom M-Pesa Daraja STK Push callbacks alongside international Stripe checkouts with real-time entitlement gating.

Direct Cloud Storage

Pre-signed direct S3/R2 binary transfers keep media data off identity servers while strictly coordinating per-user quota allocation.

Fast Track

2. 5-Minute Quickstart

1

Register Developer Application

Navigate to the Developer Console and create an application. Provide your callback redirect URI (e.g. https://myapp.com/api/auth/callback) to obtain your client_id.

2

Generate an API Key (Optional)

For backend daemons or server-to-server microservices, create a scoped API key in the Developer Portal. Keys follow the pattern frik_live_... and are hashed with SHA-256 before storage.

3

Verify Health via cURL

Verify connectivity with the production API gateway:

bash
curl -X GET "https://api.frica.id/healthz"

Expected response: {"status":"UP","timestamp":"2026-10-04T...","version":"1.0.0"}

Official WordPress plugin

Login with Frica ID for WordPress

Let visitors sign in to your WordPress site with their Frica account. The free, open-source plugin adds a login button, a shortcode and explicit account connection from WordPress Profile.

Requires WordPress 6.6+, PHP 8.0+ with OpenSSL, and HTTPS. The WordPress.org directory submission is awaiting review; install the packaged ZIP from GitHub Releases.

  1. Install the packaged plugin. Download the frica-id-login ZIP from the release assets. In WordPress, open Plugins → Add New → Upload Plugin, install it and activate it.
  2. Copy your site's callback. Open Settings → Frica ID and copy the exact callback URL shown there. The public callback uses WordPress's admin-post handler; visitors do not need administrator access.
  3. Register your WordPress app. Open Developer applications, create an app, then open its configuration. Add the copied URL under Allowed Redirect URIs and save. Use the standard public OAuth client with openid profile email.
  4. Enable Frica sign-in. Copy the app's Client ID into WordPress Settings → Frica ID and enable sign-in. The plugin uses PKCE and does not need a client secret, API key or KYC purchase.
  5. Choose registration behavior. To admit new members, enable the plugin's new-subscriber option and WordPress Settings → General → Anyone can register. New accounts receive the Subscriber role. Keep registration disabled when only existing members should connect.
  6. Test as a visitor. Sign out of WordPress and use Login with Frica ID. To connect an existing WordPress account, first sign in with its WordPress credentials, then use Profile → Frica ID connection. An email match does not automatically merge accounts.
Callback example — replace example.com with your site
https://example.com/wp-admin/admin-post.php?action=frica_id_callback
WordPress shortcode
[frica_id_login]

What members will see

Members approve the requested Frica profile access and return to the same WordPress site. New WordPress usernames use the Public Frica ID, with a suffix when a local username is already taken. Existing generated usernames can upgrade on a later sign-in; custom usernames stay unchanged.

WordPress passwords and email recovery remain available. WordPress logout ends the local WordPress session. It does not sign the member out of Frica globally.

Troubleshooting

Callback mismatch: copy the exact URL from plugin Settings, including the action query parameter. Existing email: sign in to that WordPress account first and connect it from Profile. Verification failure: match the support reference in Settings → Frica ID → Last sign-in diagnostic and check its code, stage and reason. Share those safe fields with support; keep tokens, passwords and authorization codes private.

Exclude wp-login.php and wp-admin/admin-post.php from page caching. Test membership, security and MFA plugins on your site before opening sign-in to visitors. Multisite supports per-site activation and existing site members.

SSO & Identity

3. OAuth 2.1 & OpenID Connect (SSO)

Integrate single sign-on across mobile apps, SPAs, and web applications using standard OpenID Connect.

OpenID Connect Discovery

Frica ID exposes RFC 8414 discovery configuration at:

http
GET https://api.frica.id/.well-known/openid-configuration
Endpoint ParameterURI Value
issuerhttps://api.frica.id
authorization_endpointhttps://frica.id/oauth/authorize
token_endpointhttps://api.frica.id/oauth/token
userinfo_endpointhttps://api.frica.id/oauth/userinfo
jwks_urihttps://api.frica.id/.well-known/jwks.json
introspection_endpointhttps://api.frica.id/oauth/introspect
revocation_endpointhttps://api.frica.id/oauth/revoke

Authorization Code with PKCE Flow

Step 1: Construct the authorization URL and redirect the user:

text
https://frica.id/oauth/authorize?
  response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://myapp.com/callback
  &scope=openid%20profile%20email%20offline_access
  &state=random_state_string_xyz
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256

Step 2: Exchange the authorization code for RS256 Access & ID tokens:

bash
curl -X POST "https://api.frica.id/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "code=fcode_abc123xyz" \
  -d "redirect_uri=https://myapp.com/callback" \
  -d "code_verifier=ORIGINAL_RANDOM_CODE_VERIFIER"

Refresh Token Rotation (RTR)

When renewing tokens, Frica ID invalidates the previous refresh token and issues a new one. This ensures instant revocation if token leakage is detected:

bash
curl -X POST "https://api.frica.id/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "refresh_token=frt_current_refresh_token"
Machine-to-Machine

4. Scoped API Keys

For server-side scripts, cron jobs, and background workers, authenticate using HTTP header X-API-Key:

http
GET /v1/users/me HTTP/1.1
Host: api.frica.id
X-API-Key: frik_live_a89bc213fe09...

API Scopes Reference

ScopeCategoryGranted Permission
users:readIdentityRead user profiles, verification status, and public metadata
users:writeIdentityUpdate user profiles, preferences, and custom attributes
storage:readStorageList drive objects, view metadata, and generate pre-signed download URLs
storage:writeStorageInitialize file uploads, commit parts, and move files to trash
billing:readBillingQuery active subscriptions, usage quotas, and payment histories
kyc:writeKYCCreate hosted verification sessions
kyc:readKYCRead session status and application verification details
kyc:checkKYCRequest an attribute check; this does not establish identity approval
webhooks:manageDeveloperRegister, test, and rotate webhook listener endpoints
*WildcardFull administrative authority (Super Admin keys only)
Identity verification

5. KYC & Live Verification

Create a session from your backend, redirect the applicant to Frica's hosted document and camera flow, and verify the outcome on your server. Passing liveness or returning to your application is not KYC approval.

For your own Frica ID profile, use Identity Verification in your account. The backend integration below is for developer applications; hosted-session fees are charged to the organization wallet.

1. Prepare your application

  1. Create an active application in the Developer Portal.
  2. Generate an API key with kyc:write and kyc:read; add kyc:check only for attribute checks. Store it as FRICA_KYC_API_KEY on your backend, never in browser or mobile bundles.
  3. Fund the organization wallet. The current hosted-session creation fee is 0.45 FRICA, charged atomically with session creation. An insufficient or held balance prevents creation.
  4. Register an HTTPS webhook endpoint for the same application. Supply and securely retain your signing secret; endpoint registration returns only a masked preview.

2. Create and persist a hosted session

typescript
// Server only. Derive externalUserId from your authenticated account.
export async function createKycSession(externalUserId: string) {
  const key = process.env.FRICA_KYC_API_KEY;
  if (!key) throw new Error('Configure the server KYC API key');

  const response = await fetch(
    'https://api.frica.id/v1/developer/kyc/sessions',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'X-API-Key': key },
      body: JSON.stringify({
        requestedTier: 'TIER_2',
        returnUrl: 'https://your-app.example/kyc/return',
        cancelUrl: 'https://your-app.example/kyc/cancel',
        customReference: 'your-stored-onboarding-reference',
        customer: { externalUserId }
      })
    }
  );
  const body = await response.json();
  if (!response.ok || body.success !== true || !body.data?.sessionUrl) {
    throw new Error('KYC session creation was not confirmed; reconcile before retrying');
  }
  // Persist sessionId, applicationId and externalUserId in your backend.
  // Keep sessionUrl/clientSecret confidential; send sessionUrl only to this applicant.
  return body.data;
}

REST responses use { success, requestId, timestamp, data }; read the session from data. Send sessionUrl only to its applicant. Hosted sessions expire after 24 hours. Each creation POST makes a new session and charge: customReference is correlation, not an idempotency key. Reconcile an uncertain response before creating another session.

The hosted URL contains a session client secret. Exclude it from analytics, logs and public links. The hosted page handles document capture, camera permission, liveness and submission.

3. Read status and confirm the decision

bash
curl --fail-with-body \
  "https://api.frica.id/v1/developer/kyc/sessions/YOUR_SESSION_ID" \
  -H "X-API-Key: $FRICA_KYC_API_KEY"

# Read verificationId from your signed submission event, then fetch details:
curl --fail-with-body \
  "https://api.frica.id/v1/developer/kyc/verifications/YOUR_VERIFICATION_ID" \
  -H "X-API-Key: $FRICA_KYC_API_KEY"
Session statusApplication handling
ACTIVEAwait submission; do not grant verified access.
SUBMITTEDKeep the applicant pending while compliance review is required.
APPROVED / REJECTEDFetch application-bound verification details on your backend before applying the decision.
EXPIRED / CANCELLEDDo not continue capture or treat the session as verified.

The return URL may contain session_id, status, verification_id and custom_reference. These browser parameters are untrusted. Match applicationId and externalUserId to your stored session, verify signed events and fetch verification details with kyc:read. A failed lookup, missing record, unavailable claim or UNDER_REVIEW status must leave the applicant unverified.

4. Receive signed KYC events

Subscribe to kyc.session.created, kyc.submitted, kyc.approved and kyc.rejected. Event type is in X-Frica-Event. The signed body contains event-specific fields such as sessionId,verificationId, externalUserId and status; it is not an { event, data } envelope. Use the raw-body signature verifier below.

Persist accepted events before acknowledging with 2xx, deduplicate by X-Frica-Delivery-Id and business identifiers, and handle out-of-order notifications. Monitor failed deliveries in the Developer Portal and reconcile through authenticated reads; do not assume automatic redelivery. Never grant approval from the event header alone. The created event contains a secret-bearing hosted URL, so avoid logging raw bodies.

5. Custom capture and current limits

Prefer the hosted page. Custom capture starts with POST /v1/kyc/sessions/:sessionId/livenessand { clientSecret }. Send frames to POST /v1/kyc/sessions/:sessionId/liveness/:id/frameswith { clientSecret, sequence, image }. Follow the returned instruction, quality, progress and sequence. Keep one frame in flight; on a lost acknowledgement, retry the identical frame and sequence.

Liveness expires after 180 seconds and is bound to the hosted session. After complete: true, preserve the exact final selfie bytes. Submit to POST /v1/kyc/sessions/:sessionId/submit with the hosted clientSecret, legal name, date of birth, nationality, document type and number, country of issuance, frontDocumentDataUrl, optional backDocumentDataUrl, selfieDataUrl andlivenessData: { sessionId: livenessSessionId }. Use JPEG, PNG or WebP data URLs. Dates use YYYY-MM-DD; the current engine requires a minimum age of 18. The server proof is consumed once; client-supplied scores and gesture booleans are not proof.

Successful biometrics currently route to compliance review. Government document authenticity, OCR/MRZ and live sanctions/PEP screening are not supplied by these face models. The number-only check below currently returns verified: false; unperformed watchlist and MRZ checks return null. Do not interpret model scores as calibrated identity probabilities.

bash
curl --fail-with-body -X POST \
  "https://api.frica.id/v1/developer/kyc/check" \
  -H "X-API-Key: $FRICA_KYC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Amina",
    "lastName": "Example",
    "dateOfBirth": "1995-06-15",
    "documentType": "NATIONAL_ID",
    "documentNumber": "YOUR_DOCUMENT_NUMBER",
    "country": "KE"
  }'

Production setup and troubleshooting

Operators must deploy PostgreSQL, Redis and the private biometric worker, apply migrations, fund the organization wallet and enable the server wallet release gate. Configure KYC_BIOMETRIC_URL and a shared KYC_BIOMETRIC_TOKENof at least 32 characters on the server. Set KYC_BIOMETRIC_VALIDATED=true only after measured capture and attack-resistance acceptance testing. These controls belong on the server, not in public frontend environment variables.

For 400, check required fields, dates and expired or consumed proofs. For 401/403, check the session secret, API key and scopes. For 429, respect the retry delay. A 503 or unavailable camera must not become verification success. Use HTTPS camera permission and restart expired liveness rather than reusing its proof.

Event Streaming

6. Webhooks & Real-Time Event Verification

Frica ID delivers secure HTTP POST notifications whenever critical state changes occur. Every payload is cryptographically signed using your secret key.

Verification Headers

• X-Frica-Signature: Header formatted as t=UNIX_SECONDS,v1=HEX_HMAC_SHA256, calculated over ${timestamp}.${rawBody}.

• X-Frica-Timestamp: Unix epoch timestamp in seconds. Reject clock drift beyond 300 seconds and deduplicate deliveries to limit replay.

Node.js Raw-Body Signature Verifier

typescript
import { createHmac, timingSafeEqual } from 'node:crypto';

// Mount express.raw({ type: 'application/json', limit: '256kb' })
// on this webhook route BEFORE express.json(). Pass req.body here.
export function verifyFricaWebhook(
  rawBody: Buffer,
  signatureHeader: string,
  timestampHeader: string,
  webhookSecret: string
): boolean {
  if (!Buffer.isBuffer(rawBody) || !webhookSecret) return false;
  const parts = /^t=(\d+),v1=([a-f0-9]{64})$/i.exec(signatureHeader);
  if (!parts || parts[1] !== timestampHeader) return false;

  const timestamp = Number(timestampHeader);
  if (!Number.isSafeInteger(timestamp) ||
      Math.abs(Math.floor(Date.now() / 1000) - timestamp) > 300) {
    return false;
  }

  const expected = createHmac('sha256', webhookSecret)
    .update(timestampHeader + '.')
    .update(rawBody)
    .digest();
  const received = Buffer.from(parts[2], 'hex');
  return received.length === expected.length &&
    timingSafeEqual(received, expected);
}
Drive & Objects

7. Unified Cloud Storage & Drive API

Frica ID uses pre-signed direct S3 uploads. Files are transferred directly from client to Cloudflare R2 / AWS S3 without bottlenecking the identity servers.

Upload Lifecycle

Step 1: Request pre-signed upload URL and reserve user quota:

bash
curl -X POST "https://api.frica.id/v1/storage/upload/init" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "annual-report.pdf",
    "mimeType": "application/pdf",
    "sizeBytes": 10485760,
    "service": "frica_drive"
  }'

Step 2: Stream binary file directly to S3 / Cloudflare R2:

bash
curl -X PUT "https://r2.cloudflarestorage.com/frica-drive-production/usr_1/...?X-Amz-Signature=..." \
  -H "Content-Type: application/pdf" \
  --data-binary "@annual-report.pdf"
Monetization & Limits

8. Entitlements & Multi-Provider Billing

Query current subscription tiers, feature flags, and storage caps for any authenticated user.

bash
curl -X GET "https://api.frica.id/v1/entitlements/me" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Example JSON response:

json
{
  "tier": "PRO",
  "storageQuotaBytes": 107374182400,
  "maxConcurrentSessions": 10,
  "apiRateLimitRpm": 1200,
  "features": {
    "custom_domains": true,
    "audit_export": true,
    "priority_support": true,
    "mpesa_direct_billing": true
  }
}
SDKs & Code

9. Multi-Language SDK Snippets

Choose your stack to view drop-in client code for making authenticated API calls and verifying webhooks:

tsx
// npm install @frica/id-sdk
import React from 'react';
import { createFricaClient, FricaAuthProvider, FricaSignInButton, useFricaAuth } from '@frica/id-sdk';

// 1. Initialize Client
const client = createFricaClient({
  clientId: 'fcli_your_client_id',
  redirectUri: 'https://myapp.com/auth/callback',
  defaultScope: 'openid profile email phone'
});

// 2. Wrap App with Provider
export function App({ children }: { children: React.ReactNode }) {
  return <FricaAuthProvider client={client}>{children}</FricaAuthProvider>;
}

// 3. Drop in Branded Button Component
export function LoginCard() {
  const { user, isAuthenticated, logout } = useFricaAuth();

  if (isAuthenticated && user) {
    return (
      <div>
        <p>Logged in as {user.name} ({user.email})</p>
        <button onClick={logout}>Sign Out</button>
      </div>
    );
  }

  return <FricaSignInButton theme="dark" size="md" />;
}
Capacity

10. Rate Limiting & Standards

API requests are monitored using distributed Redis sliding-window counters. Quota information is returned in standard HTTP response headers:

X-RateLimit-Limit

Total allowable requests per window

X-RateLimit-Remaining

Requests remaining in current window

X-RateLimit-Reset

Epoch timestamp when limit resets

Troubleshooting

11. RFC 7807 Error Code Reference

All errors returned by api.frica.id strictly adhere to the RFC 7807 Problem Details specification:

json
{
  "type": "https://id.frica.com/errors/status-401",
  "title": "UNAUTHORIZED",
  "status": 401,
  "detail": "Invalid credentials or expired Bearer token",
  "instance": "/users/me",
  "requestId": "req_1791136234043",
  "timestamp": "2026-10-04T20:00:00.000Z"
}
StatusTitleResolution & Handling
400BAD_REQUESTPayload validation failed. Check required parameters and formats.
401UNAUTHORIZEDMissing or expired access token. Execute token refresh or verify API key.
403FORBIDDENInsufficient scope. Request additional scope permissions for client or key.
404NOT_FOUNDTarget user, object, or developer resource does not exist.
429TOO_MANY_REQUESTSRate limit exceeded. Pause requests until Retry-After elapses.
500INTERNAL_SERVER_ERRORServer exception. Include the requestId when contacting Frica Support.

Ready to start building?

Create your developer account, generate credentials, and start building with Frica ID.

Go to Developer Portal