Integrate business and KYB data

Get KYB and business data from Merchant Profiler as JSON or PDF, or create an account and let Coris run your onboarding checks.

Run Merchant Profiler and read the result as JSON or PDF, or create an account and let Coris run your onboarding checks. This path suits lenders, marketplaces and underwriting fintechs.

Use Merchant Profiler
  1. Step 1POST /merchant_profiler/
  2. Step 2callback_url or GET /merchant_profiler/
  3. Step 3GET /establishment/{establishment_id}/merchant_profiler_pdf_report/
Create an account
  1. Step 1POST /accounts/
  2. Step 2POST /accounts/{account_id}/persons/
  3. Step 3GET /accounts/{account_id}/

Merchant Profiler or accounts?

Use Merchant Profiler if you will make the decision yourself. Create an account if you want Coris to run your onboarding rules and keep monitoring.

CompareMerchant ProfilerAccount
You getJSON or PDF dataA stored merchant
After the callNothing further runsMonitored by default

Setup checklist

Create a key, then finish the items for your integration.

Endpoints you use

JobEndpoint
Get a profile as JSON or PDFCreate a Merchant Profiler request, Get a PDF report
Create the merchantCreate an account
Add an ownerAdd a person to an account
Score the riskRun a CorShield evaluation

Data you send and receive

You sendYou receive
Business name or DBA, address, phone, websiteestablishment, legal_entity and data sections
callback_urlThe result, posted to you
reference_id and owners, for an accountAn account with status and monitored

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

Use Merchant Profiler

Step 1: Request a profile with a callback

Coris posts the full result to callback_url when every section finishes.

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" },
    "website": "https://www.acmeplumbing.example",
    "callback_url": "https://hooks.acmeplatform.example/coris/callback"
  }' | jq -r '.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",
        },
        "website": "https://www.acmeplumbing.example",
        "callback_url": "https://hooks.acmeplatform.example/coris/callback",
    },
)
response.raise_for_status()
request_id = response.json()["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" },
    website: "https://www.acmeplumbing.example",
    callback_url: "https://hooks.acmeplatform.example/coris/callback",
  }),
});
if (!created.ok) throw new Error(await created.text());
const { request_id: requestId } = await created.json();

Try it.

Step 2: Fall back to polling

Poll if you cannot receive callbacks, or one is late.

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 3 (optional): Save a PDF report

Get a link to a PDF report.

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 it.

Sections in unavailable_sections are missing from the report. If pdf_report_url is null, try again later.

Create an account

Step 1: Create the account

Coris matches the business, creates the account and runs your onboarding monitoring. With no rules set up, nothing runs.

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

REQUEST_ID=$(curl -s -X POST "$CORIS_BASE_URL/accounts/" \
  -H "Authorization: Token $CORIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"reference_id\": \"$REFERENCE_ID\",
    \"business_name\": \"Acme Plumbing Co.\",
    \"address\": { \"address_type\": \"Registration\", \"city\": \"San Jose\", \"state\": \"CA\", \"country\": \"US\" }
  }" | jq -r '.request_id')

ACCOUNT_ID=$(poll /accounts/ "$REQUEST_ID" | jq -r '.id')
reference_id = f"acme-plumbing-{int(time.time())}"

response = requests.post(
    f"{BASE_URL}/accounts/",
    headers=HEADERS,
    json={
        "reference_id": reference_id,
        "business_name": "Acme Plumbing Co.",
        "address": {
            "address_type": "Registration",
            "city": "San Jose",
            "state": "CA",
            "country": "US",
        },
    },
)
response.raise_for_status()
account_id = poll("/accounts/", response.json()["request_id"])["id"]
const referenceId = `acme-plumbing-${Date.now()}`;

const accountJob = await fetch(`${BASE_URL}/accounts/`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({
    reference_id: referenceId,
    business_name: "Acme Plumbing Co.",
    address: { address_type: "Registration", city: "San Jose", state: "CA", country: "US" },
  }),
});
if (!accountJob.ok) throw new Error(await accountJob.text());
const accountRequestId = (await accountJob.json()).request_id;
const accountId = (await poll("/accounts/", accountRequestId)).id;

Try it.

Step 2: Add the owner

Send one call per owner. Enabled checks start here.

curl -s -X POST "$CORIS_BASE_URL/accounts/$ACCOUNT_ID/persons/" \
  -H "Authorization: Token $CORIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Jordan",
    "last_name": "Sample",
    "email": "[email protected]",
    "relationship": { "title": "Owner", "owner": true, "percent_ownership": 100 }
  }' | jq
response = requests.post(
    f"{BASE_URL}/accounts/{account_id}/persons/",
    headers=HEADERS,
    json={
        "first_name": "Jordan",
        "last_name": "Sample",
        "email": "[email protected]",
        "relationship": {"title": "Owner", "owner": True, "percent_ownership": 100},
    },
)
print(response.status_code, response.json())
const person = await fetch(`${BASE_URL}/accounts/${accountId}/persons/`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({
    first_name: "Jordan",
    last_name: "Sample",
    email: "[email protected]",
    relationship: { title: "Owner", owner: true, percent_ownership: 100 },
  }),
});
console.log(person.status, await person.json());

Try it.

Step 3: Read the account

Read the account to see its state.

curl -s "$CORIS_BASE_URL/accounts/$ACCOUNT_ID/" \
  -H "Authorization: Token $CORIS_API_KEY" | jq '{id, reference_id, name, source}'
response = requests.get(f"{BASE_URL}/accounts/{account_id}/", headers=HEADERS)
print(response.json())
const account = await fetch(`${BASE_URL}/accounts/${accountId}/`, { headers: HEADERS });
console.log(await account.json());

Try it.

Read the response

This shortened account response shows whether to hold the merchant. The Quickstart covers the Merchant Profiler response.

{
  "id": "c47f0e21-6a9b-4d38-a2e5-1b8d3f90c6a7",
  "reference_id": "acme-plumbing-1762300800",
  "name": "Acme Plumbing Co.",
  "status": "active",
  "monitored": true,
  "source": "api"
}
FieldWhat to do
idCarry it, or reference_id, into later calls.
statusHold merchants that are not active.
monitoredIf true, register a webhook.

Add a CorShield risk score (optional)

Get a 0–100 risk score with either integration. Set first_party to false unless Coris has enabled that model. The default is true, and a disabled model returns 400.

REQUEST_ID=$(curl -s -X POST "$CORIS_BASE_URL/corshield/fraud_model/" \
  -H "Authorization: Token $CORIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "first_party": false,
    "third_party": true,
    "reference_id": "acme-plumbing-001",
    "business_name": "Acme Plumbing Co.",
    "email": "[email protected]",
    "phone_number": "408-555-0142",
    "website": "https://www.acmeplumbing.example",
    "address": { "city": "San Jose", "state": "CA", "country": "US" },
    "principal": { "first_name": "Jordan", "last_name": "Sample" }
  }' | jq -r '.request_id')

poll /corshield/fraud_model/ "$REQUEST_ID" | jq '{reference_id, third_party_models, portal_link}'
response = requests.post(
    f"{BASE_URL}/corshield/fraud_model/",
    headers=HEADERS,
    json={
        "first_party": False,
        "third_party": True,
        "reference_id": "acme-plumbing-001",
        "business_name": "Acme Plumbing Co.",
        "email": "[email protected]",
        "phone_number": "408-555-0142",
        "website": "https://www.acmeplumbing.example",
        "address": {"city": "San Jose", "state": "CA", "country": "US"},
        "principal": {"first_name": "Jordan", "last_name": "Sample"},
    },
)
response.raise_for_status()
result = poll("/corshield/fraud_model/", response.json()["request_id"])
print(result["third_party_models"])
const shieldStart = await fetch(`${BASE_URL}/corshield/fraud_model/`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({
    first_party: false,
    third_party: true,
    reference_id: "acme-plumbing-001",
    business_name: "Acme Plumbing Co.",
    email: "[email protected]",
    phone_number: "408-555-0142",
    website: "https://www.acmeplumbing.example",
    address: { city: "San Jose", state: "CA", country: "US" },
    principal: { first_name: "Jordan", last_name: "Sample" },
  }),
});
if (!shieldStart.ok) throw new Error(await shieldStart.text());
const result = await poll("/corshield/fraud_model/", (await shieldStart.json()).request_id);
console.log(result.third_party_models);

Try it.

A higher fraud_score means more risk. Verify a business at onboarding shows how to read the result.

Build a profile piece by piece (optional)

To choose the match yourself, use Search businesses. Then fetch only the sections you want: website, reviews, ratings, industry, adverse media, registrations or agents.

What to do next

Your needGuide
Decide on an applicationVerify a business at onboarding
Review a merchant in depthUnderwrite a merchant
Keep watching after approvalMonitor your portfolio

For your risk, compliance and security reviewers: How Coris checks work, Monitoring and alerts explained and Security and compliance.

FAQ

What happens if Coris finds no match?

Merchant Profiler creates a new business record from your request. Check legal_entity.status before you treat it as verified.

Can I start from an establishment_id?

Yes, on POST /accounts/. That form links the business, returns 201 with the account and runs no onboarding.

Can I rerun Merchant Profiler from an establishment.id?

No. POST /merchant_profiler/ ignores establishment_id. Use the /establishment/ endpoints for a known business.

Next: Fintechs, ISOs and sponsor banks


Did this page help you?