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.
- Step 1POST /
merchant_profiler/ - Step 2callback_url
or GET / merchant_profiler/ - Step 3GET /
establishment/ {establishment_id}/ merchant_ profiler_ pdf_ report/
- Step 1POST /
accounts/ - Step 2POST /
accounts/ {account_id}/ persons/ - 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.
| Compare | Merchant Profiler | Account |
|---|---|---|
| You get | JSON or PDF data | A stored merchant |
| After the call | Nothing further runs | Monitored by default |
Setup checklist
Create a key, then finish the items for your integration.
- Create a sandbox key in the portal.
- Merchant Profiler: get your callback secret.
- Merchant Profiler: expose a public HTTPS endpoint and verify callback signatures.
- Accounts: set up your onboarding rules. Coris can help.
- Swap to a production key and host.
Endpoints you use
| Job | Endpoint |
|---|---|
| Get a profile as JSON or PDF | Create a Merchant Profiler request, Get a PDF report |
| Create the merchant | Create an account |
| Add an owner | Add a person to an account |
| Score the risk | Run a CorShield evaluation |
Data you send and receive
| You send | You receive |
|---|---|
| Business name or DBA, address, phone, website | establishment, legal_entity and data sections |
callback_url | The result, posted to you |
reference_id and owners, for an account | An 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();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" | jqresponse = 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());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;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 }
}' | jqresponse = 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());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());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"
}| Field | What to do |
|---|---|
id | Carry it, or reference_id, into later calls. |
status | Hold merchants that are not active. |
monitored | If 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);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 need | Guide |
|---|---|
| Decide on an application | Verify a business at onboarding |
| Review a merchant in depth | Underwrite a merchant |
| Keep watching after approval | Monitor 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?
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?
establishment.id?No. POST /merchant_profiler/ ignores establishment_id. Use the /establishment/ endpoints for a known business.
Updated about 3 hours ago