Checking business applications against the Fraud Consortium
Last updated: July 8, 2026
Once your organization is an active member, you have three ways to check applicants against the consortium pool. Pick the one that fits the moment in your flow:
Surface | When to use | Endpoint |
|---|---|---|
Inline business search | You're already running a full Baselayer KYB. No extra integration needed. |
|
Ad-hoc check | You want a quick lookup using raw identifiers, with no full KYB and no |
|
Batch check | Portfolio sweep, monitoring run, or any case where you want to check many businesses at once. |
|
Membership is required for all three: callers without an active membership (Pending or Inactive status) receive 403 Forbidden.
How matching works
A consortium check is multi-pass — every supplied identifier triggers its own match attempt, all run in parallel. A single business can be matched on more than one signal at once (e.g. TIN and phone).
Signal | Match type | Notes |
|---|---|---|
| Exact (Postgres FK) | Resolved by Baselayer's entity graph. |
TIN (EIN) | Exact | Digits-only normalization. |
Phone | Exact | 10 digits, country code stripped. |
Exact | Lowercased + trimmed. | |
Website | Exact | Registered domain only — protocol, |
Address | Exact (Postgres FK) | Resolved address ID via Smarty. |
Business name | Fuzzy (Typesense) | Similarity score between 0 and 1. |
Officer name | Fuzzy (Typesense) | Similarity score between 0 and 1. |
Exact identifiers always rank above fuzzy name matches. Results are returned sorted: exact-before-fuzzy, then by score descending, then by recency. Each match tells you which inputs hit it via matched_on, whose type is one of TIN, PHONE, EMAIL, WEBSITE, BUSINESS_NAME, OFFICER, ADDRESS, or BUSINESS_ID.
What a hit means for your scores: A consortium hit during a POST /searches call forces KYB Rating = F and Risk Rating = F. This is intentionally more severe than OFAC — the consortium signal is peer-validated fraud and we treat it as ground truth.
1. Inline business search
If you're already running POST /searches, you don't need to do anything new. Once your org is active, the consortium check runs automatically as part of the existing pipeline and the hit shows up alongside OFAC, OIG, and other watchlists in watchlist_hits.
Response shape
{
"watchlist_hits": [
{
"code": "BFC",
"name": "Baselayer Fraud Consortium",
"count": 2,
"details": [
{
"exclusion_id": "1111-...-...",
"reported_on": "2026-06-02",
"business_name": "Fraud Corp",
"tin": "123456789",
"primary_owner": { "first_name": "John", "last_name": "Doe" },
"secondary_owner": null,
"closure_reason": "WPF",
"product_type": "MCA",
"amount": 50000.0,
"score": 1.0
},
{
"exclusion_id": "2222-...-...",
"score": 1.0
}
]
}
]
}Field | Meaning |
|---|---|
| Always |
| Always |
| Total number of matched exclusion records returned. |
| The matched records. Each entry carries the same anonymized exclusion detail as an ad-hoc match (see below). |
| 1.0 for exact identifier hits; less than 1.0 for fuzzy name hits. |
Contributing org identity is never revealed — you see the count and the anonymized fraud record, but not which member reported it.
details[] is an open object per hit; the fields shown mirror the ad-hoc match record documented in the next section.
2. Ad-hoc check
Use this when you want to screen a single applicant before committing to a full KYB run, or when you don't have a Baselayer business_id yet.
Endpoint
POST /consortium/searches
Content-Type: application/jsonSend the identifiers you have in a JSON body. All fields are optional, but you must supply at least one identifier.
Field | Type | Notes |
|---|---|---|
| string | Business tax ID (EIN), digits only. |
| string[] | One or more phone numbers. |
| string[] | One or more email addresses. |
| string | Registered domain (e.g. |
| string | Only matched when paired with another identifier — name-only search is not supported. |
| uuid | Baselayer business UUID, if you have one. |
| object[] | Each: |
| object |
|
Example request
curl -X POST 'https://api.baselayer.com/consortium/searches' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"tin": "123456789",
"emails": ["contact@acme.com"],
"officers": [{ "first_name": "John", "last_name": "Smith", "state": "TX" }]
}'Response
{
"matches": [
{
"exclusion_id": "1111-...-...",
"reported_on": "2026-03-15",
"business_name": "Fraud Corp",
"business_address": "123 Main St, Austin, TX 78701",
"tin": "123456789",
"email": "contact@acme.com",
"website": null,
"primary_owner": {
"first_name": "John",
"middle_name": null,
"last_name": "Doe",
"phone_number": null,
"address": null
},
"secondary_owner": null,
"product_type": "MCA",
"closure_status": "WO",
"closure_reason": "WPF",
"amount": 50000.0,
"score": 1.0,
"matched_on": [
{ "type": "TIN", "value": "123456789" },
{ "type": "EMAIL", "value": "contact@acme.com" }
]
}
],
"total_matches": 1
}Field | Meaning |
|---|---|
| Confidence in |
| Every input that hit this record, each with a |
| Number of distinct exclusion records returned. |
Each match also carries the full anonymized exclusion detail — business name and address, TIN, primary/secondary owner, product type, closure status and reason, amount, and the relevant dates.
A no-match returns total_matches: 0 with an empty matches array. This is not an error.
Edge cases
business_nameon its own: name-only matching is intentionally unsupported (too many false positives). Pair the name with at least one other identifier.No active membership:
403 Forbidden.Malformed body / invalid values (e.g. a bad state code):
422 Unprocessable Content.
3. Batch check
Use this for portfolio sweeps, monitoring runs, or any time you want to screen many businesses at once. The batch is processed asynchronously and results stream to a downloadable JSONL file.
Flow
POST /consortium/searches/batches → 202 + batch_id
GET /consortium/searches/batches/{id} → poll status & progress
GET /consortium/searches/batches/{id}/download → 307 redirect to signed URLStep 1 — submit the CSV
curl -X POST 'https://api.baselayer.com/consortium/searches/batches' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-F 'file=@my_portfolio.csv;type=text/csv'Response (202 Accepted):
{
"id": "4d0a91e0-...-...",
"state": "PENDING",
"progress": null,
"completed_count": null,
"total_count": 5000,
"created_at": "2026-06-14T22:14:00Z"
}CSV format
One check per row. At least one identifier per row is required. Allowed columns:
Column | Notes |
|---|---|
| Your own correlation id. Echoed back in each result so you can join rows back to your records. Highly recommended. |
| Business tax ID (EIN). |
| |
| |
| |
| |
| |
| |
| Two-letter state code. |
| |
| |
| Two-letter state code. |
| |
| Baselayer business UUID, if you have one. |
Limits
40,000 rows max per batch. Larger files are rejected with
422 Unprocessable Content.200 MiB max file size. Larger files are rejected with
413 Content Too Large.If you have more than 40,000 rows to screen, split into multiple batches.
Step 2 — poll for status
curl 'https://api.baselayer.com/consortium/searches/batches/4d0a91e0-...' \
-H 'Authorization: Bearer YOUR_API_KEY'{
"id": "4d0a91e0-...-...",
"state": "EXECUTING",
"progress": 0.42,
"completed_count": 2100,
"total_count": 5000,
"created_at": "2026-06-14T22:14:00Z"
}state will be one of: PENDING, EXECUTING, COMPLETED, FAILED, or CANCELLED. We recommend polling every 30–60 seconds. Typical 5,000-row batch completes in 1–3 minutes.
Step 3 — download the results
Once state is COMPLETED:
curl -L 'https://api.baselayer.com/consortium/searches/batches/4d0a91e0-.../download' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-o results.jsonlThe endpoint returns a 307 redirect to a short-lived signed Google Cloud Storage URL. Use curl -L (or your client's equivalent) to follow the redirect and stream the file to disk.
Result format — JSONL
One JSON object per input row, in submission order:
{"reference_id":"row-1","input":{"ein":"123456789"},"matches":[{"score":1.0,"matched_on":[{"type":"TIN","value":"123456789"}],"exclusion_id":"1111-...","business_name":"Fraud Corp","closure_reason":"WPF","reported_on":"2026-03-15"}],"total_matches":1,"error":null}
{"reference_id":"row-2","input":{"email":"safe@example.com"},"matches":[],"total_matches":0,"error":null}
{"reference_id":"row-3","input":{"business_id":"not-a-uuid"},"matches":[],"total_matches":0,"error":"invalid business_id: not-a-uuid"}Field | Meaning |
|---|---|
| Your supplied correlation id (or |
| The parsed identifiers that were checked. |
| Same shape as the ad-hoc check |
| Distinct records matched for this row. |
| Set instead of matches if this single row failed to process. The rest of the batch is unaffected. |
Per-row failures don't kill the batch. A malformed TIN or unparseable UUID in row 47 will return an error on that row and the other 4,999 rows still process normally.
Reading and acting on a hit
When you get a consortium hit, the response gives you enough anonymized context to make a decision:
closure_reasontells you what kind of fraud the contributing org saw (WPFfirst-party,WIFidentity,WSFsynthetic).reported_ontells you how recent the contribution is.matched_ontells you *which* identifier(s) triggered the hit — useful for distinguishing "shared phone with a known fraudster" from "exact TIN match."amountandproduct_typegive you a sense of the scale and product context.
You'll never see *which* organization contributed the record. If you need to dispute or investigate further, contact your Baselayer account manager.