In fintech, many services rely on overlapping slices of customer data: KYC/identity verification, credit score / risk, and transactional or account-level details (balances, deposits, payments). But these typically live in separate systems, controlled by different teams, using different APIs and data models.
When external partners (originators, lending platforms, credit bureaus) or internal modules need a cohesive “customer profile,” engineers often must call three or more APIs, reconcile identifiers, handle mismatched schemas, and implement access controls individually. That leads to brittle integrations, duplicated logic, and inconsistent behavior.
INSART’s approach is to build a Unified Customer Financial Profile API — a single, secure API surface that internally resolves identities, joins data sources, enforces fine-grained access, and exposes a coherent profile to downstream consumers. For lending or risk systems, this simplifies integration: they no longer need to stitch KYC, credit, and account systems themselves — they simply call one API.
In this case study, I’ll walk through INSART’s design and implementation of this API — covering identity resolution, data modeling, hybrid API strategy (GraphQL + REST + gRPC), security (JWT, roles), and how fintech partners consume it for risk, scoring, and onboarding.
Problem Definition & Requirements
Functional Requirements
-
Given a customer identifier (e.g. email, customer ID, external reference), return a unified profile including:
-
KYC status and identity attributes (name, DOB, address, verification timestamps)
-
Credit / risk indicators (score, bureau flags, internal risk tags)
-
Account / transaction metadata (principal bank, deposit balance, active accounts)
-
Relationships (linked accounts, business structure, co-signers)
-
-
Support partial view modes: e.g. some partners only see non-PII attributes, some see full fields.
-
Provide both real-time access (low latency) and bulk / batched access (for partner onboarding or syncing).
-
Allow flexible access control: field-level restrictions, role-based control, and consumption limits.
Non-Functional & Operational Requirements
-
High throughput & low latency (sub-100 ms for profile queries)
-
Schema stability and versioning — as credit logic or KYC rules evolve
-
Strong security: JWT-based authentication, role-based access control, encryption in transit & at rest
-
Audit / logging for every access (who accessed what, when)
-
Extensibility — one can graft in new data sources (e.g. alternative credit provider, alternative KYC provider) without rewriting consumers
-
Support for multiple API styles — some clients prefer GraphQL, others REST or gRPC (for internal microservices)

Architectural Design
High-Level Components
┌──────────────┐
│ External │
│ Clients / │
│ Partners │
└──────┬───────┘
│
Unified Profile API Layer
│ (GraphQL / REST / gRPC)
┌──────────┴──────────┐
│ Identity Resolution │
│ Engine / Graph │
└──────────┬──────────┘
┌────────┴────────┐
│ Data Adapters / │
│ Source Connectors│
└───┬───┬────┬────┘
│ │ │
KYC DBs Credit API Account Ledger
(identity) (score) (balances, txns)
-
Unified Profile API Layer: The exposed endpoint (GraphQL + REST + gRPC) with orchestration and aggregation logic.
-
Identity Resolution Engine / Graph: A service that maintains a graph of identity links (email, customer_id, external ref) and resolves to canonical IDs.
-
Data Adapters / Source Connectors: Modular modules for KYC DB, credit bureau APIs, internal account ledger systems.
Identity Graph & Resolution
At the core is an identity graph: a table (or graph DB) that maps multiple identifiers to a canonical internal profile_id. Example:
|
profile_id |
|
external_id |
customer_id |
linked_ids |
|---|---|---|---|---|
|
p_10001 |
alice@finapp.com |
ext_872 |
cust_9001 |
[cred_id_12, acc_45] |
When a request arrives with email = alice@finapp.com or customer_id = cust_9001, resolver returns profile_id = p_10001, then downstream joins fetch data from various sources keyed on that profile_id or associated identifiers.
This entity resolution supports:
-
Merging duplicates
-
Onboarding new identity links
-
Historical lineage
API Strategy: GraphQL + REST + gRPC
-
GraphQL (Hasura-based, or custom GraphQL server) — allows clients to request only the fields they need; aggregate multiple sub-entities in one request. Hasura supports remote schema stitching, actions, and permission rules.
-
REST / gRPC internal endpoints — for synchronous services or microservice-to-microservice calls. For example, internal modules (credit scoring engine) may call gRPC GetProfileBasic(profile_id) with minimal overhead. Combining hybrid API style allows best-of-all-worlds.
GraphQL resolvers delegate to internal services (REST/gRPC) or execute database joins, depending on the requested field. Clients can mix and match.
Authorization & Security (JWT + RBAC + Field-Level)
-
Clients must authenticate using JWTs, issued through your identity provider (IDP).
-
JWT includes custom claims such as:
{
"sub": "client_app_123",
"scope": ["profile:read", "credit:read"],
"x-profile-role": "partner_viewer",
"x-user-id": "user_567"
}
-
GraphQL engine (Hasura or custom) reads these claims and enforces role-based permissions and row-level security (RLS) — e.g. a partner token can only see profile_ids they’re authorized for, and only certain fields.
-
Field-level security ensures sensitive fields (e.g. SSN, KYC doc status) are only exposed to authorized roles.
-
All accesses are logged with metadata (client, profile_id, fields accessed, timestamp) for audit trails.
Data Freshness, Caching & Staleness Controls
-
For fields that change frequently (balances, latest transactions), fetch live from account DB with in-memory or Redis caching and TTL.
-
For relatively static or slowly changing data (credit score, KYC), cache in profile store with refresh windows (e.g. hourly or daily).
-
Use GraphQL caching strategies (persisted queries, depth limits, complexity bounds) to guard performance.
Versioning & Schema Evolution
-
The GraphQL schema is versioned. Breaking changes are introduced via versioned APIs (v1, v2) or feature toggles.
-
The identity graph and adapters are decoupled so new data sources can be added without affecting API consumers.
Sample API Scenarios & Payloads
GraphQL Query Example
Client calls:
query GetCustomerProfile($email: String!) {
profileByEmail(email: $email) {
profile_id
identity {
name
dob
kyc_status
}
credit {
score
bureau_flags
}
accounts {
account_id
balance
currency
last_txn_date
}
}
}
Backend flow:
-
Resolve profile_id from email (identity graph service).
-
Pull identity data from KYC DB.
-
Fetch credit data using the credit adapter.
-
Query account ledger DB for accounts associated with profile_id.
-
Merge and return.
REST / gRPC Access
Internal service calls:
rpc GetProfile (GetProfileRequest) returns (ProfileResponse);
{
"profile_id": "p_10001",
"identity": { ... },
"credit": { ... }
}
Useful for internal modules needing just a small slice without GraphQL overhead.
Error Handling & Fallbacks
If one data source fails (e.g. credit API down), the API can still return partial profile with a warnings array:
{
"profile_id": "...",
"identity": { ... },
"credit": null,
"accounts": [ ... ],
"warnings": ["Credit service unavailable"]
}
Clients must be tolerant of partial profiles but also informed.
Implementation & Delivery
Data Adapters
INSART builds adapters for each data system:
-
KYC Adapter: interfaces with internal KYC DB or external verification provider (e.g. Onfido). Supports GetIdentity(profile_id) and ListRecentVerifications(profile_id).
-
Credit Adapter: calls external credit bureau APIs or internal scoring systems. Supports GetCredit(profile_id) and RefreshCredit(profile_id).
-
Account Adapter: queries internal ledger systems; joins account tables, transaction logs, and balance caches.
Each adapter implements a standard interface:
interface ProfileAdapter {
get(profile_id, fields: List<String>): PartialProfile
}
Adapters are versioned independently and accept field hints to minimize over-fetching.
Identity Graph Service
A small service (Node.js / Go) that:
-
Maintains mappings between identifiers and profile_id
-
Allows linking/unlinking logic (when user adds another identifier)
-
Supports lookup APIs (resolveByEmail, resolveByCustomerId)
-
Runs deduplication jobs and conflict resolution workflows
GraphQL Layer via Hasura or Custom
One option is using Hasura:
-
Hasura connects to the primary Postgres DB storing identity / profile tables.
-
Other data sources (credit, account) can be exposed via remote schemas or Actions.
-
Permissions are enforced at the GraphQL metadata layer using JWT claims (x-hasura-role, x-hasura-allowed-roles) and column-level checks.
-
Custom business logic (field transformations, aggregations) can be implemented via Actions or event triggers.
Alternatively, a custom GraphQL server (e.g. Apollo / GraphQL-java) can route to adapters and enforce authorization through middleware.

Monitoring, Logging & Audits
-
All profile API requests are logged (client, fields, profile_id, latency).
-
Use instruments like OpenTelemetry to trace downstream calls to each adapter.
-
Monitor error rates per adapter (e.g. credit failures) and fallback frequency.
-
Audit logs stored in immutable S3 / append-only store for regulatory record.
Fintech Value & Use Cases
For Lending / Risk Assessment
A fintech lending partner can call /profileByCustomerId(customer_id) and get unified data:
-
KYC status = verified
-
Credit score = 680
-
Active accounts = 2, total balance = $25,000
Now they can decide: approve, reject, or request more documents — with one API call, not three.
Partner Onboarding & APIs
Third-party apps (e.g. affiliate platforms, co-branded financial apps) can integrate once with this profile API. They don’t have to integrate KYC provider, credit bureau, or your ledger — you encapsulate it. That dramatically lowers friction and errors.
Internal Billing, Pricing & Segmentation
Your product and pricing modules can call the profile API to segment users by risk band, account size, or KYC recency — enabling dynamic pricing, limit increases, or onboarding workflows.
Real-Time Features & UX
Because the API can deliver near-real-time data, you can build internal dashboards, portal views, or partner dashboards showing “Your Risk Tier: Silver / Gold” or “Eligible Credit Increase” — all based on live data.
Challenges & Mitigations
-
Data sync consistency: Some data (credit, transactions) lags. Mitigate via freshness flags, timestamps, and versioning.
-
Schema drift across sources: Adapters must guard against schema changes upstream (e.g. credit provider adding new fields). Use schema validation and adapter contracts.
-
Latency hotspots: If one adapter is slow, it may degrade full profile latency. Use parallel calls, timeouts, or fallback caching.
-
Authorization complexity: Managing field-level access across roles can become complex. Use declarative permission frameworks and tests.
-
Security & privacy risk: Because this API is a high-value target, enforce strict rate limits, input sanitization, and encryption end-to-end.

Measurement & Outcomes
By deploying this unified profile API, a typical fintech partner might achieve:
-
50% fewer integration errors — partners no longer implement identity stitching
-
Time-to-integration reduced by 70%
-
Consistent risk decisioning across partners
-
Operational cost savings — fewer reconciliation scripts, data pipelines simplified
-
Higher security and compliance posture — single audit surface
In internal pilots, we’ve observed each loan originator partner launching in weeks instead of months.
Conclusion
A Unified Customer Financial Profile API is more than a convenience — it’s a platform builder. It abstracts away the complexity of identity, KYC, credit, and accounts so that downstream systems (partners, scoring, onboarding) can operate simply, reliably, and securely.
INSART’s approach — identity graph, adapter pattern, hybrid API surface, JWT + role-based security — delivers just that. For fintechs building lending, risk, or embedded finance offerings, this architecture can be the foundation on which many products confidently rest.









