# API specs of Markets data sharing
This page describes API specs that Markets need to expose for data sharing. If you do not use the data sharing feature, you can skip this page.
# Overview
The following information needs to be provided by a Market to Credify for Market integration.
- List of basic profile data
- Definition of custom data scope
Endpoints:
- Push claims API endpoint
- User segment API endpoint
- Offer evaluation API endpoint
- Encryption claims API endpoint
- Offer filtering API endpoint
Of these 5 APIs, only Push claims API
and Encryption claims API
endpoints are necessary when a Market shares data. The other APIs are optional if you want to deal with customized financial offers for you users. Markets will need to implement these APIs using our SDK, then register their endpoints on our Dashboard. To add the endpoints, please go to Settings -> General information -> Update -> Account details -> update API Base Url
Product without an offer | Product with an offer | |
---|---|---|
with MP data | Push claim API & Encrypted claim API | All 5 integration APIs are necessary |
without MP data | Only Intent API integration / Retail portal | Only Intent API integration / Retail portal |
TIP
We have developed a skeleton service to minimize the integration work, which handles most common logic and SDK interaction. We provide the skeleton service in Node.js as of May 2022. All you would have to do is to fork the repository and to customize some database interaction only.
The following sections relating to the backend implementation are not necessary if you use the skeleton service. You can find example implementation of the above 5 APIs in our skeleton.
# List of basic profile data
Each Market needs to set what piece of profile information it can provide with Service Providers. Here is a list of data points:
phone
- e.g.,+84381234567
email
- e.g.,test@credify.one
address
- e.g.,1st Floor, Savimex Building, 194 Đ. Nguyễn Công Trứ, Phường Nguyễn Thái Bình, Quận 1, Thành phố Hồ Chí Minh
gender
- e.g.,male
/female
dob
- e.g.,1991-12-25
name
- e.g.,Nguyển Minh Long
If a Market does not provide any of user data, then it can be empty list.
# Definition of custom data scope
Scope definition is a custom data schema that reflects a user's personal information outside of basic profile data that a Market will share with Financial Institutions. An example of custom data scope would be an end user's monthly transaction. The users need to agree to share these data for the data transmission to go through. By doing so, users can get better financial product as it leverages their data to improve their credibility. Financial Institutions can filter scope definition to find which users fit their target.
This property is optional. You may provide this information to enhance UX to offer financial products for right user segments.
# Push claims API endpoint
This API is used to verify that a Market has data about a specific user. When a user logs in to your platform with their Credify account, you can call this API with their internal ID in your system. The API will then map this ID with the user's unique identifier in the ServiceX system, verifying that the user has consented to sharing their data with your Market.
# API definition
# Implementation
Here is what we need you to do in this API endpoint
- Extract the internal user ID and their Credify ID from the API request payload.
- Save the mapping between the internal user ID and their Credify ID in your database, as well as Credify's scopes with your internal data. This mapping will be crucial for the following steps
- Compose a claim object with the user's data stored in your database.
- Push the claim object using our SDK's push claims function. The function will take in your organization ID, the Credify ID from the user, and the claim object.
- You will receive a commitment object signifying that you have data about the user. Save this object in your database for later use.
- Return a 200 status code with response body being the Credify ID of the user.
To implement these steps, you need to find the user associated with the internal ID passed from the request, save their Credify ID along with the user, and save the commitments returned by the SDK's function in your database.
A claim token is a JWT whose payload is following.
scope_name: string
- Unique scope name registered in serviceX Dashboard
- In case of standard claims, this will be predefined, such as
profile
,phone
,email
, andaddress
.
scope_hash: string
- sha256 hashed value, like
CREDIT_SCORE_SCOPE_HASH
/EMAIL_SCOPE_HASH
in the above description.
- sha256 hashed value, like
timestamp: integer
- Unix timestamp in sec
user_claim_token: string
- JWT to ensure this data attachment is coming from a Market. Its payload is
provider_id
,user_id
,scope_name
,scope_hash
, andtimestamp
, likeCREDIT_SCORE_CLAIM_TOKEN_PAYLOAD
/EMAIL_CLAIM_TOKEN_PAYLOAD
.
- JWT to ensure this data attachment is coming from a Market. Its payload is
Here is a sample.
// In the case of your `custom` claims
const CREDIT_SCORE_SCOPE_DATA = {
YOUR_CLAIM_A: "CLAIM_A_VALUE",
YOUR_CLAIM_B: "CLAIM_B_VALUE",
YOUR_CLAIM_C: "CLAIM_C_VALUE",
"credit-score:commitment": "SECRET_RANDOM_COMMITMEMT",
};
const CREDIT_SCORE_SCOPE_HASH = sha256(JSON(CREDIT_SCORE_SCOPE_DATA));
const CREDIT_SCORE_CLAIM_TOKEN_PAYLOAD = {
scope_name: "credit-score",
user_id: "a6367e50-2d4c-...", // passed from the mobile SDK
scope_hash: CREDIT_SCORE_SCOPE_HASH,
timestamp: 1612197298,
};
// In the case of your `standard` claims
const EMAIL_SCOPE_DATA = {
email: "test@credify.one",
email_commitment: "SECRET_RANDOM_COMMITMEMT",
};
const EMAIL_SCOPE_HASH = sha256(JSON(CREDIT_SCORE_SCOPE_DATA));
const EMAIL_CLAIM_TOKEN_PAYLOAD = {
scope_name: "email",
user_id: "a6367e50-2d4c-...", // passed from the mobile SDK
scope_hash: EMAIL_SCOPE_HASH,
timestamp: 1612197298,
};
- Node.js
- Java
- .NET
const pushClaims = async (req, res, { db, credify }) => {
// This step is optional if you want to authenticate your internal requests
const validRequest = await authenticateInternalUser(db, req);
if (!validRequest) {
return res.status(401).send({ message: "Unauthorized" })
}
const organizationId = process.env.APP_ID
if (!organizationId) {
return res.status(400).send({ message: "Please recheck config - organization ID" })
}
if (!req.body.id || !req.body.credify_id) {
return res.status(400).send({ message: "Invalid body" })
}
try {
const localId = req.body.id
const credifyId = req.body.credify_id
// save the mapping between credify ID and your user's local ID for later use.
await updateUserId(db, localId, credifyId);
// get the claim object from the data mapping between credify and your system
const claims = await fetchUserClaimObject(db, localId, credifyId, [], false);
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
await delay(3000);
const commitments = await credify.claims.push(
organizationId,
credifyId,
claims
)
await upsertCommitments(db, credifyId, commitments);
res.json({ credifyId })
} catch (e) {
res.status(500).send({ message: e.message })
}
}
module.exports = pushClaims
# User segment API endpoint
This API is used to retrieve user segments based on specified thresholds. When Service Providers configure offers in Credify dashboard, they can set thresholds for certain criteria (e.g. credit score, income, etc.). The User Segment API can then be used to check how many users in your system fall under each threshold. This can help Service Providers better target their offers to users who meet their criteria.
# API definition
# Implementation
What you have to do by yourself is
- Validate access token attached in the API request authorization header. To validate, use the
introspectToken
function in our SDK. The scope you need to check isoidc_client:read_user_counts
. - Extract offer conditions from our API request payload
- Count the number of users who meet each extracted offer conditions.
- Return the counts, with each count stands for the index of the corresponding condition.
- Node.js
- Java
- .NET
const countUsers = async (req, res, { db, credify }) => {
if (process.env.CONTEXT_ENV !== "Jest") {
const token = extractToken(req)
try {
const validToken = await credify.auth.introspectToken(
token,
PERMISSION_SCOPE.COUNT_USER
)
if (!validToken) {
return res.status(401).send({ message: "Unauthorized" })
}
} catch (e) {
return res.status(500).send({ message: e.message })
}
}
let conditions = req.body.conditions || [{}]
conditions = conditions.map((c) => {
if (c === null) return {}
else return c
})
const requiredCustomScopes = req.body.required_custom_scope || []
if (conditions.length !== requiredCustomScopes.length) {
return res.status(400).send({ message: "Conditions length and scopes length must be the same" })
}
const claims = fetchUserClaimObject
// this function queryUsers would be your internal API
const counts = await queryUsers(db, conditions, requiredCustomScopes)
try {
const response = {
data: {
counts: counts,
},
}
res.json(response)
} catch (e) {
res.status(500).send({ message: e.message })
}
}
module.exports = countUsers
# Offer evaluation API endpoint
We use this endpoint when finalizing offer redemption. This evaluation result will be sent to the Service Provider for them to give right benefits to users.
# API definition
# Implementation
What you have to do by yourself is
- Validate access token attached in the API request authorization header. To validate, use the
introspectToken
function in our SDK. The scope you need to check isindividual:read_evaluated_offer
- Get the credify ID, condition, required custom scopes from the request
- Fetch the claim object associated with the credify user, using the same flow as previously in
Push claim flow
. By now, you should have credify ID mapped with your local user ID. Please refer to the implementation in our skeleton for this part. - Evaluate the user's level based on their profile and provided offer conditions using the
evaluateOffer
function in our SDK - Return the evaluation result
- Node.js
- Java
- .NET
const evaluate = async (req, res, { db, credify }) => {
if (process.env.CONTEXT_ENV !== "Jest") {
const token = extractToken(req)
try {
const validToken = await credify.auth.introspectToken(
token,
PERMISSION_SCOPE.READ_EVALUATED_OFFER
)
if (!validToken) {
return res.status(401).send({ message: "Unauthorized" })
}
} catch (e) {
return res.status(500).send({ message: e.message })
}
}
if (!req.body.credify_id || !req.body.scopes) {
return res.status(400).send({ message: "Invalid body" })
}
let conditions = req.body.conditions || [{}]
conditions = conditions.map((c) => {
return c === null ? {} : c
})
const requiredCustomScopes = req.body.required_custom_scopes || []
try {
const credifyId = req.body.credify_id;
const sharedScopes = req.body.scopes
const userSharedClaims = await fetchUserClaimObject(db, undefined, credifyId, sharedScopes, false);
const result = await credify.offer.evaluateOffer(
conditions,
requiredCustomScopes,
userSharedClaims
)
const response = {
data: {
rank: result.rank,
used_scopes: result.usedScopes,
requested_scopes: result.requestedScopes,
},
}
res.json(response)
} catch (e) {
res.status(500).send({ message: e.message })
}
}
module.exports = evaluate
# Encrypted claims API endpoint
This API is used to encrypt user data with the Financial Institution's key and pass the encrypted data to the Financial Institution through OpenID Connect. This helps ensure that the user's data remains secure and private during transmission to the Financial Institution. The API is called after the user has agreed to share their data, and can be used to help protect sensitive information from unauthorized access.
# API definition
API doc for Encrypted claims API
# Implementation
What you have to do by yourself is
- Validate access token attached in the API request authorization header. To validate, use the
introspectToken
function in our SDK. The scope you need to check isoidc_client:read_encrypted_claims
- Extract the
requestToken
and theapprovalToken
from the request - Use the
validateRequest
function in our SDK to validate theaccessToken
,requestToken
,approvalToken
. This will return thepublicKey
andscopes
of the request. Save these 2 values for the next steps. - Fetch the claim object associated with the credify user, using the same flow as previously in
Push claim flow
. Then use theencrypt claim
function in our SDK to encrypt theclaim object
with thepublicKey
you got earlier - Get the
verification info
of your user (an example of this implementation can be found in our skeleton), and theencryptedClaims
from the previous step, and return the result.
The below code is an example of how a claim object composer look like. We recommend you to follow the format of the object below so a Service Provider can decrypt the claims given from your side without any unexpected issues.
const claims = {};
claims["b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name-history-data"] = {
"3285592c-9aaf-4182-bff5-941ce5dac483:purchase-count": `${user.transactionsCount}`,
"3285592c-9aaf-4182-bff5-941ce5dac483:total-payment-amount": `${user.totalPaymentAmount}`,
"b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name-history-data:commitment": "some randon value"
};
claims["b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name%20score"] = {
"3285592c-9aaf-4182-bff5-941ce5dac483:market-name%20score": `${user.creditScore}`,
"b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name%20score:commitment": "some random value"
};
// This is the sample response object after processing the claim object
const data = {
_claim_names: {
'3285592c-9aaf-4182-bff5-941ce5dac483:purchase-count': 'b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name-history-data',
'3285592c-9aaf-4182-bff5-941ce5dac483:total-payment-amount': 'b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name-history-data',
'b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name-history-data:commitment': 'b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name-history-data',
'3285592c-9aaf-4182-bff5-941ce5dac483:market-name%20score': 'b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name%20score',
'b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name%20score:commitment': 'b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name%20score'
},
_claim_resources: {
'b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name-history-data': {
JWT: "JWE encoding the claim object"
},
'b09e8f99-6d89-4e7d-83ea-a43a1787b3e0:market-name%20score': {
JWT: "JWE encoding the claim object"
}
}
}
- Node.js
- Java
- .NET
const encryptClaims = async (req, res, { db, credify }) => {
let publicKey = "";
let scopes = [];
if (process.env.CONTEXT_ENV !== "Jest") {
const accessToken = extractToken(req)
if (accessToken === "") {
return res.status(401).send({ message: "Unauthorized" })
}
if (
!req.body.user_id ||
!req.body.request_token ||
!req.body.approval_token
) {
return res.status(400).send({ message: "Invalid body" })
}
const requestToken = req.body.request_token
const approvalToken = req.body.approval_token
try {
const result = await credify.claims.validateRequest(
accessToken,
requestToken,
approvalToken
)
publicKey = result.publicKey;
scopes = result.scopes;
} catch (e) {
return res.status(500).send({ message: e.message })
}
} else {
publicKey = req.headers["public-key"];
scopes = req.headers["scopes"].split(",");
}
try {
const credifyId = req.body.user_id
const claims = await fetchUserClaimObject(db, undefined, credifyId, scopes, true);
const encrypted = await credify.claims.encrypt(claims, publicKey)
// please check our skeleton for implementation of fetchVerificationInfo
const verificationInfo = await fetchVerificationInfo(db, credifyId);
const data = {
data: {
verification_info: verificationInfo,
claims: encrypted,
},
}
res.send(data)
} catch (e) {
res.status(500).send({message: e.message})
}
}
module.exports = encryptClaims
# Offer filtering API endpoint
The Offer Filtering API provides a list of available offers for each user with evaluation result. This is needed for the Credify SDK to efficiently display the offers on your platform.
# API definition
API doc for Offer filtering API
# Implementation
What you have to do by yourself is
- Validate access token attached in the API request authorization header. To validate, use the
introspectTokenReturnResult
function in our SDK. The scope you need to check isclaim_provider:read_filtered_offers
. Be careful, we use a different function here, and you need to check the token extra carefully here. Example code:
const isCorrectAudience = tokenResult.data.aud[0].includes("/api/offers/filter")
const isCorrectScope = tokenResult.data.scope.split(" ").includes(PERMISSION_SCOPE.READ_FILTER_OFFER)
if (!tokenResult.data.active || !isCorrectAudience || !isCorrectScope) {
return res.status(401).send({message: "Unauthorized"})
}
- Get the
credifyID
,localID
, andoffers
from the request - Fetch the claim object associated with the credify user, using the same flow as previously in
Push claim flow
. - Use the
evaluateOffer
function in our SDK then evaluate the claim object, the offer's condition, and offer's required custom scopes, then return the result
- Node.js
- Java
- .NET
const filterOffer = async (req, res, { db, credify }) => {
if (process.env.CONTEXT_ENV !== "Jest") {
try {
const token = extractToken(req)
const tokenResult = await credify.auth.introspectTokenReturnResult(
token,
PERMISSION_SCOPE.READ_FILTER_OFFER
)
// check token is active, has correct audience, has correct scope
const isCorrectAudience = tokenResult.data.aud[0].includes("/api/offers/filter")
const isCorrectScope = tokenResult.data.scope.split(" ").includes(PERMISSION_SCOPE.READ_FILTER_OFFER)
if (!tokenResult.data.active || !isCorrectAudience || !isCorrectScope) {
return res.status(401).send({message: "Unauthorized"})
}
} catch (e) {
return res.status(500).send({message: e.message})
}
}
const credifyId = req.body.credify_id
const localId = req.body.local_id
const offers = req.body.offers
if (!credifyId && !localId) {
return res.status(400).send({ message: "No ID found" })
}
if (offers === undefined) {
const response = {
data: {
offers: [],
},
}
return res.status(200).json(response)
}
try {
if (!offers.length) {
const response = {
data: {
offers: [],
},
}
return res.status(200).json(response)
}
const userClaims = await fetchUserClaimObject(db, localId, credifyId, [], false);
const personalizedOffers = []
await Promise.all((offers.map(async (offer) => {
const result = await credify.offer.evaluateOffer(
offer.conditions,
offer.required_custom_scopes || [],
userClaims
)
console.log("evaluate offer res: ", JSON.stringify(result))
const formattedOffer = {
...offer,
evaluation_result: {
rank: result.rank,
used_scopes: result.usedScopes,
requested_scopes: result.requestedScopes,
},
}
if (result.rank > 0) {
// Return only qualified offers
personalizedOffers.push(formattedOffer)
}
})))
const response = {
data: {
offers: personalizedOffers,
},
}
res.json(response)
} catch (e) {
res.status(500).send({ message: e.message })
}
}
module.exports = filterOffer