Underwrite a merchant

Pull a full merchant profile, review website, reviews, industry and licenses, and open an account for approved merchants.

One Merchant Profiler request returns the registration, website, reviews, industry, licenses and personnel data you need to approve a merchant.

  1. Step 1ProfilePOST /merchant_profiler/
  2. Step 2PollGET ?request_id=
  3. Step 3Review sections
  4. Step 4Save a reportGET /establishment/{establishment_id}/merchant_profiler_pdf_report/
  5. Step 5Create the accountPOST /accounts/
  6. Step 6Add personsPOST /accounts/{account_id}/persons/
Product view of a merchant profile for a fictional business, with panels for business info, website insights, online reviews, litigation, adverse media and a street-level location check

Product view from coris.ai, simplified for illustration. It shows (1) registration, industry codes and the CorShield score, (2) reviews and litigation, and (3) adverse media and the location check.

Step 1: Set up your key

Set your key and base URL once.

export CORIS_BASE_URL="https://sandbox-api.coris.ai/api/v0_2"
export CORIS_API_KEY="YOUR_API_KEY"
import requests

BASE_URL = "https://sandbox-api.coris.ai/api/v0_2"
HEADERS = {"Authorization": "Token YOUR_API_KEY"}
const BASE_URL = "https://sandbox-api.coris.ai/api/v0_2";
const HEADERS = {
  Authorization: "Token YOUR_API_KEY",
  "Content-Type": "application/json",
};

The samples below wait with the polling helper. Expand it, and run it too.

Polling helper
# Polls an async endpoint and prints the body once it returns 200.
# 206, 425, 429 and 500 all mean wait and poll again. A 500 re-queues the job.
poll() {
  delay=5
  for _ in $(seq 1 30); do
    http_status=$(curl -s -o /tmp/coris_body -w '%{http_code}' \
      -H "Authorization: Token $CORIS_API_KEY" \
      "$CORIS_BASE_URL$1?request_id=$2")
    if [ "$http_status" = "200" ]; then cat /tmp/coris_body; return 0; fi
    case "$http_status" in
      206|425|429|500) ;;
      *) echo "HTTP $http_status: $(cat /tmp/coris_body)" >&2; return 1 ;;
    esac
    sleep "$delay"
    delay=$((delay * 3 / 2)); [ "$delay" -gt 30 ] && delay=30
  done
  echo "Timed out" >&2; return 1
}
import time

# 206, 425, 429 and 500 all mean wait and poll again. A 500 re-queues the job.
RETRYABLE = (206, 425, 429, 500)

def poll(path, request_id, attempts=30):
    """Polls an async endpoint and returns the JSON body once it returns 200."""
    delay = 5
    for _ in range(attempts):
        response = requests.get(
            f"{BASE_URL}{path}", params={"request_id": request_id}, headers=HEADERS
        )
        if response.status_code == 200:
            return response.json()
        if response.status_code not in RETRYABLE:
            response.raise_for_status()
        time.sleep(delay)
        delay = min(delay * 1.5, 30)
    raise TimeoutError(f"{path} did not finish")
// 206, 425, 429 and 500 all mean wait and poll again. A 500 re-queues the job.
const RETRYABLE = [206, 425, 429, 500];

// Polls an async endpoint and returns the JSON body once it returns 200.
async function poll(path, requestId, attempts = 30) {
  let delay = 5000;
  for (let i = 0; i < attempts; i++) {
    const url = `${BASE_URL}${path}?request_id=${requestId}`;
    const response = await fetch(url, { headers: HEADERS });
    if (response.status === 200) return response.json();
    if (!RETRYABLE.includes(response.status)) {
      throw new Error(`HTTP ${response.status}: ${await response.text()}`);
    }
    await new Promise((resolve) => setTimeout(resolve, delay));
    delay = Math.min(delay * 1.5, 30000);
  }
  throw new Error(`${path} did not finish`);
}

Step 2: Request the profile

With no data_types, Coris runs your workspace's default sections.

REQUEST_ID=$(curl -s -X POST "$CORIS_BASE_URL/merchant_profiler/" \
  -H "Authorization: Token $CORIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "business_name": "Acme Plumbing Co.",
    "address": {
      "address_type": "Registration",
      "city": "San Jose",
      "state": "CA",
      "country": "US"
    },
    "phone_number": "408-555-0142",
    "website": "https://www.acmeplumbing.example"
  }' | jq -r '.request_id')

echo "$REQUEST_ID"
response = requests.post(
    f"{BASE_URL}/merchant_profiler/",
    headers=HEADERS,
    json={
        "business_name": "Acme Plumbing Co.",
        "address": {
            "address_type": "Registration",
            "city": "San Jose",
            "state": "CA",
            "country": "US",
        },
        "phone_number": "408-555-0142",
        "website": "https://www.acmeplumbing.example",
    },
)
response.raise_for_status()
request_id = response.json()["request_id"]
print(request_id)
const created = await fetch(`${BASE_URL}/merchant_profiler/`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({
    business_name: "Acme Plumbing Co.",
    address: {
      address_type: "Registration",
      city: "San Jose",
      state: "CA",
      country: "US",
    },
    phone_number: "408-555-0142",
    website: "https://www.acmeplumbing.example",
  }),
});
if (!created.ok) throw new Error(await created.text());
const { request_id: requestId } = await created.json();
console.log(requestId);

Try POST /merchant_profiler/ in the API explorer.

Step 3: Read the profile

Poll until the profile is ready, and keep establishment.id for the next steps.

PROFILE=$(poll /merchant_profiler/ "$REQUEST_ID")
ESTABLISHMENT_ID=$(printf '%s' "$PROFILE" | jq -r '.establishment.id')
printf '%s' "$PROFILE" | jq '{establishment, legal_entity, portal_link}'
profile = poll("/merchant_profiler/", request_id)
establishment_id = profile["establishment"]["id"]
print(profile["establishment"], profile["legal_entity"], profile.get("portal_link"))
const profile = await poll("/merchant_profiler/", requestId);
const establishmentId = profile.establishment.id;
console.log(profile.establishment, profile.legal_entity, profile.portal_link);

Step 4: Save a PDF report

Fetch one report for your underwriting file. pdf_report_url can be null with is_complete set to false.

curl -s "$CORIS_BASE_URL/establishment/$ESTABLISHMENT_ID/merchant_profiler_pdf_report/" \
  -H "Authorization: Token $CORIS_API_KEY" | jq
response = requests.get(
    f"{BASE_URL}/establishment/{establishment_id}/merchant_profiler_pdf_report/",
    headers=HEADERS,
)
response.raise_for_status()
print(response.json())
const report = await fetch(
  `${BASE_URL}/establishment/${establishmentId}/merchant_profiler_pdf_report/`,
  { headers: HEADERS },
);
console.log(await report.json());

Try GET /establishment/{establishment_id}/merchant_profiler_pdf_report/ in the API explorer.

Step 5: Create the account

Open the account for a merchant you approve, using establishment.id from step 3.

REFERENCE_ID="acme-plumbing-$(date +%s)"

ACCOUNT=$(curl -s -X POST "$CORIS_BASE_URL/accounts/" \
  -H "Authorization: Token $CORIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"reference_id\": \"$REFERENCE_ID\", \"establishment_id\": \"$ESTABLISHMENT_ID\"}")

ACCOUNT_ID=$(printf '%s' "$ACCOUNT" | jq -r '.id')
echo "$ACCOUNT_ID"
reference_id = f"acme-plumbing-{int(time.time())}"

response = requests.post(
    f"{BASE_URL}/accounts/",
    headers=HEADERS,
    json={"reference_id": reference_id, "establishment_id": establishment_id},
)
response.raise_for_status()
account_id = response.json()["id"]
print(account_id)
const referenceId = `acme-plumbing-${Date.now()}`;

const createdAccount = await fetch(`${BASE_URL}/accounts/`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({ reference_id: referenceId, establishment_id: establishmentId }),
});
if (!createdAccount.ok) throw new Error(await createdAccount.text());
const account = await createdAccount.json();
const accountId = account.id;
console.log(accountId);

Try POST /accounts/ in the API explorer.

Read the response

Each section of the profile from step 3 tells you what to check before you approve.

SectionWhat to do
legal_entity_registrationsCheck status and filing date.
legal_entity_licensesCheck them against the trade.
personnelCompare them with the application.
establishment_websiteCheck policies and contact details.
establishment_reviewsRead any low rating or bad review.
establishment_ratingsReview a biz_rating or website_rating of 4 or 5. 1 is best. See How to read Coris scores.
establishment_industryCompare it with the declared MCC.
establishment_adverse_mediaReview any entry.
establishment_litigation_checkerReview any entry.

A section that failed holds an error key instead of data.

Workspaces on the original API version receive establishment_rating instead of establishment_ratings.

What to do next

Decide each merchant with the example policy, one table that covers these sections and the risk score.

FAQ

How do I add a risk score?

Add business_fraud_check to data_types in step 2, and set first_party to false unless Coris has enabled that model for you. The score arrives under corshield. List every section you need, because data_types replaces your default sections.

To score separately, see Verify a business at onboarding.

Can Coris create the account for me?

Yes. Send reference_id with create_account set to true in step 2.

How do I add the merchant's owners?

Add each one after step 5 with Add a person to an account.

Next: Monitor your portfolio


Did this page help you?