# 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 profile keys that the Market can provide with Service Providers
    • e.g., phone, email, address, gender, dob, name
  • Scope definitions
  • Push claims API endpoint
  • User counts API endpoint
  • Offer evaluation API endpoint
  • Encryption claims API endpoint
  • Offer filtering API endpoint

# Skeleton service

Integration model

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, .NET, and Java as of May 2022. All you would have to do is to clone 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.


# List of profile keys that the Market can provide with Service Providers

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 Mình Long

If a Market does not provide any of user data, then it can be empty list.

# Scope definitions

This endpoint is necessary when a Market is going to share its data to enhance user experience to offer personalized financial services.

Each Market may configure scope definitions ー so called data schema, which will state what piece of custom data the Market is going to share across the Credify network. Of course the data transmission can never happen unless end-users make a consent for the data sharing. Service Providers will use the scope information to filter users on partner platforms to reach out to their potential customers.

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 endpoint is necessary when a Market is going to share some data.

This endpoint requests a data providing service (Market) to push a certificate that proves this data providing service has some data about a specified user (of course, this doesn't allow us to see the actual data). This API will call Update Claim API in its process. Push claims API will receive Market's internal user ID and an associated Credify ID, so it can connect the internal user ID and Credify ID.

When a user from your platform logs into their Credify account, we will call this endpoint.

# API definition

API doc for serviceX integration

# Implementation

Here is what we need you to do in this API endpoint

  • Extract internal user ID and its Credify ID from our API request payload
  • Save the mapping between the internal user ID and Credify ID
  • Compose a claim object with this user's data stored in your DB
  • Generate a random string value for each scope and save it for later use
  • Authenticate a client by calling Client Auth API with an API key
  • Generate a claim token (certificate) and send it via Update Claim API with the access token created above
  • Return 200 status (response body is not necessary)

What you have to do by yourself is

  • Find the user that is associated with the internal ID passed from the request
  • Save the credify ID along with the user
  • Save the commitments, which are returned by the SDK's function, in your DB

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, and address.
  • scope_hash: string
    • sha256 hashed value, like CREDIT_SCORE_SCOPE_HASH / EMAIL_SCOPE_HASH in the above description.
  • 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, and timestamp, like CREDIT_SCORE_CLAIM_TOKEN_PAYLOAD / EMAIL_CLAIM_TOKEN_PAYLOAD.

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 express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const { Credify } = require("@credify/nodejs");

// You obtain these on the serviceX dashboard.
const signingPrivateKey = `-----BEGIN PRIVATE KEY-----
your private key...
-----END PRIVATE KEY-----`;
const apiKey = "YOUR_API_KEY";
const id = "YOUR_ORGANIZATION_ID";

const app = express();
app.use(bodyParser.json());
app.use(cors());


app.post("/api/claims/push", async (req, res) => {
  // mode is `sandbox` or `production`
  const credify = await Credify.create(signingPrivateKey, apiKey, { mode: "sandbox" });

  const internalId = req.body.id;
  const credifyId = req.body.credify_id;

  // 1. Find a user with `internalId`
  const user = ...;

  // 2. Update the user to save `credifyId` in your DB

  // 3. Compose a claim object with this user's data stored in your DB
  const claims = ...;
  // This is an example:
  // const claims = {
  //   "phone": {
  //     "phoneNumber": "+84381234567"
  //   },
  //   "credit-score": {
  //     "scope": 124,
  //     "fraud": false,
  //     "updatedAt": "2021-02-16 10:25:14.599+07"
  //   },
  //   "transactions": {
  //     "totalCount": 142,
  //     "totalPaymentAmount": 8911,
  //     "currency": "usd"
  //   }
  // }


  // 4. Send the digest data of the claims and obtain the commitments
  const commitments = await credify.claims.push(id, credifyId, claims);

  // 5. Save the commitments in your DB

  return res.status(200).json(response);
});

# User segment API endpoint

This endpoint is necessary when a Market is going to share some data.

Since Credify possesses zero user data, Credify need to call API of Markets to know how many users fall under the specified threshold when a Service Provider is configuring offers on serviceX Dashboard. This API is to be used in this component.

image

# API definition

API doc for serviceX integration

# Implementation

What you have to do by yourself is

  • Validate access token attached in the API request authorization header
  • Extract offer conditions from our API request payload
  • Count the number of users who meet the extracted offer conditions
  • Return the counts
  • Node.js
  • Java
  • .NET
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const { Credify } = require("@credify/nodejs");

// You obtain these on the serviceX dashboard.
const signingPrivateKey = `-----BEGIN PRIVATE KEY-----
your private key...
-----END PRIVATE KEY-----`;
const apiKey = "YOUR_API_KEY";

const app = express();
app.use(bodyParser.json());
app.use(cors());

app.post("/api/offers/segment", async (req, res) => {
  // mode is `sandbox` or `production`
  const credify = await Credify.create(signingPrivateKey, apiKey, {
    mode: "sandbox",
  });

  // Provided you have `extractToken` function to obtain Bearer access token from Authorization header of the request.
  const token = extractToken(req);
  const isValidToken = await credify.auth.introspectToken(
    token,
    "oidc_client:read_user_counts"
  );
  if (!isValidToken) {
    return res.status(401).send({ message: "Unauthorized" });
  }

  const conditions = req.body.conditions;

  if (!conditions) {
    return res.status(400).send({ message: "Invalid body" });
  }
  
  // Your current users
  const users = ...

  // do your calculation with `conditions`
  let counts = Array(conditions.length).fill(0)
  await conditions.forEach(async (c, index) => {
    if (Object.keys(c).length === 0) {
      counts[index] = users.length
    } else {
      await users.forEach(async (u) => {
        // You user's claim composed object
        const userClaims = ...
        //*** Our SDK already supports offer evaluation for you
        const res = await credify.offer.evaluateOffer(
          [c],
          requiredCustomScopes,
          userClaims
        )
        if (res.rank === 1) {
          counts[index] += 1
        }
      })
    }
  })

  const response = {
    data: {
      counts,// return the calculation result
    },
  };
  res.json(response);
});

# Offer evaluation API endpoint

This endpoint is necessary when a Market is going to share some data.

This API is needed for users to determine which piece of data they are going to share with a Service Provider. 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

API doc for serviceX integration

# Implementation

What you have to do by yourself is

  • Find the user that is associated with the credify ID passed from the request
  • Evaluate the user's level based on their profile and provided offer conditions
  • Return the evaluation result
  • Node.js
  • Java
  • .NET
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const { Credify } = require("@credify/nodejs");

// You obtain these on the serviceX dashboard.
const signingPrivateKey = `-----BEGIN PRIVATE KEY-----
your private key...
-----END PRIVATE KEY-----`;
const apiKey = "YOUR_API_KEY";

const app = express();
app.use(bodyParser.json());
app.use(cors());

app.post("/api/offers/evaluate", async (req, res) => {
  // mode is `sandbox` or `production`
  const credify = await Credify.create(signingPrivateKey, apiKey, { mode: "sandbox" });

  // Provided you have `extractToken` function to obtain Bearer access token from Authorization header of the request.
  const token = extractToken(req);
  const isValidToken = await credify.auth.introspectToken(token, "individual:read_evaluated_offer");
  if (!isValidToken) {
    return res.status(401).send({ message: "Unauthorized" });
  }

  if (!req.body.credify_id || !req.body.conditions || !req.body.scopes) {
    return res.status(400).send({ message: "Invalid body" });
  }

  // 1. Use credify_id to find an user in your system associated with this ID
  const user = ...;

  const conditions = req.body.conditions || [{}]

  const requiredCustomScopes = req.body.required_custom_scopes || []

  // 2. Do your evaluation with `conditions` and `requiredCustomScopes`
  // Your user's claims
  const allUserClaims = ...

  // This is scope that user want to share:
  const sharedScopes = req.body.scopes

  // The claims that user want to share
  let userSharedClaims = {}
  for (let scope of sharedScopes) {
    userSharedClaims[scope] = allUserClaims[scope]
  }

  //*** Our SDK already supports offer evaluation for you
  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);
});

# Encrypted claims API endpoint

This endpoint is necessary when a Market is going to share some data.

When a user is going to share his/her data with a Service Provider, he/she will make a consent on the Credify's consent page. After the successful consent, Credify will call this encrypted claims API to retrieve the encrypted claim values that are encrypted with the Service Provider's encryption public key. Once Credify obtains the encrypted values, it will pass them to the data receiver through OpenID Connect.

# API definition

API doc for serviceX integration

# Implementation

What you have to do by yourself is

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 express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const { Credify } = require("@credify/nodejs");

// You obtain these on the serviceX dashboard.
const signingPrivateKey = `-----BEGIN PRIVATE KEY-----
your private key...
-----END PRIVATE KEY-----`;
const apiKey = "YOUR_API_KEY";

const app = express();
app.use(bodyParser.json());
app.use(cors());

app.post("/api/claims/request", async (req, res) => {
  // mode is `sandbox` or `production`
  const credify = await Credify.create(signingPrivateKey, apiKey, { mode: "sandbox" });

  // Provided you have `extractToken` function to obtain Bearer access token from Authorization header of the request.
  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 credifyId = req.body.user_id;
  const requestToken = req.body.request_token;
  const approvalToken = req.body.approval_token;
  const { publicKey, scopes } = await credify.claims.validateRequest(accessToken, requestToken, approvalToken);

  // 1. Use `credifyId` to find an user in your system associated with `credifyId`
  const user = ...;

  // 2. The commitment for user claim that already created before
  const commitment =

  // 3. Compose a claim object with `user` and `commitment`
  const claims = ...;
  
  const encrypted = await credify.claims.encrypt(claims, publicKey)
  /** sample 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"
      }
    }
  }
   **/
  
  res.json({ data: encrypted });
});

# Offer filtering API endpoint

This endpoint is necessary when a Market is going to share some data.

This returns a list of available offers for each user with evaluation result. The offers will be displayed through Credify's SDK on your platform, therefore this API implementation is needed for the SDK to handle them efficiently.

# API definition

API doc for serviceX integration

# Implementation

What you have to do by yourself is

  • Find the user that is associated with the credify ID or local ID passed from the request
  • Evaluate the user's level based on their profile and return this evaluation result along with the offers
  • Return the list of filtered offers
  • Node.js
  • Java
  • .NET
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const { Credify } = require("@credify/nodejs");

// You obtain these on the serviceX dashboard.
const signingPrivateKey = `-----BEGIN PRIVATE KEY-----
your private key...
-----END PRIVATE KEY-----`;
const apiKey = "YOUR_API_KEY";

const app = express();
app.use(bodyParser.json());
app.use(cors());

app.post("/api/offers/filter", async (req, res) => {
  // mode is `sandbox` or `production`
  const credify = await Credify.create(signingPrivateKey, apiKey, { mode: "sandbox" });

  // Provided you have `extractToken` function to obtain Bearer access token from Authorization header of the request.
  const token = extractToken(req);
  const isValidToken = await credify.auth.introspectToken(token, "claim_provider:read_filtered_offers");
  if (!isValidToken) {
    return res.status(401).send({ message: "Unauthorized" });
  }
  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);
  }

  // 1. Use `credifyId` or `localId` to find an user in your system. `localId` means ID in your system.
  const user = ...;

  // 2. Personalize the offers with `user`.
  const personalizedOffers = []
  await offers.forEach(async (offer) => {
    const userClaims = composeClaimObject(u)
    //*** Our SDK already supports offer evaluation for you
    const result = await credify.offer.evaluateOffer(
      offer.conditions,
      offer.required_custom_scopes,
      userClaims
    )

    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);
});

In case you want to have more flexible when introspecting the token (Eg: testing the API without forcing scope usage strictly or using the integration test), we have introspectTokenReturnResult function returns the introspection result like that:


  const credify = await Credify.create(signingPrivateKey, apiKey, { mode: "sandbox" });

  // Provided you have `extractToken` function to obtain Bearer access token from Authorization header of the request.
  const token = extractToken(req);
  const introspectResult = await credify.auth.introspectTokenReturnResult(
    token
  );
  let validToken;
  if (
    introspectResult.data &&
    introspectResult.data.active &&
    introspectResult.data.scope.includes("claim_provider")
  ) {
    validToken = true;
  }

  if (!validToken) {
    return res.status(401).send({ message: "Unauthorized" });
  }

Last Updated: 11/18/2022, 1:10:12 AM