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.

POST /searches (consortium hits appear in watchlist_hits)

Ad-hoc check

You want a quick lookup using raw identifiers, with no full KYB and no business_id.

POST /consortium/searches

Batch check

Portfolio sweep, monitoring run, or any case where you want to check many businesses at once.

POST /consortium/searches/batches

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

business_id

Exact (Postgres FK)

Resolved by Baselayer's entity graph.

TIN (EIN)

Exact

Digits-only normalization.

Phone

Exact

10 digits, country code stripped.

Email

Exact

Lowercased + trimmed.

Website

Exact

Registered domain only — protocol, www., and trailing slash stripped.

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.


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

code

Always BFC for consortium hits.

name

Always Baselayer Fraud Consortium.

count

Total number of matched exclusion records returned.

details[]

The matched records. Each entry carries the same anonymized exclusion detail as an ad-hoc match (see below).

details[].score

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/json

Send the identifiers you have in a JSON body. All fields are optional, but you must supply at least one identifier.

Field

Type

Notes

tin

string

Business tax ID (EIN), digits only.

phones

string[]

One or more phone numbers.

emails

string[]

One or more email addresses.

website

string

Registered domain (e.g. acme.com).

business_name

string

Only matched when paired with another identifier — name-only search is not supported.

business_id

uuid

Baselayer business UUID, if you have one.

officers

object[]

Each: first_name (required), last_name (required), state (optional, two-letter code).

address

object

street, city, state (two-letter code), zip.

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

score

Confidence in [0, 1]. 1.0 for exact identifier matches; lower for fuzzy name matches.

matched_on

Every input that hit this record, each with a type (TIN, PHONE, EMAIL, WEBSITE, BUSINESS_NAME, OFFICER, ADDRESS, BUSINESS_ID) and value. A single record matched on TIN + email produces one entry with two matched_on items.

total_matches

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_name on 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 URL

Step 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

reference_id

Your own correlation id. Echoed back in each result so you can join rows back to your records. Highly recommended.

ein

Business tax ID (EIN).

phone

email

website

business_name

officer_first_name

officer_last_name

officer_state

Two-letter state code.

address_street

address_city

address_state

Two-letter state code.

address_zip

business_id

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

The 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

reference_id

Your supplied correlation id (or null if you didn't include the column).

input

The parsed identifiers that were checked.

matches

Same shape as the ad-hoc check matches array.

total_matches

Distinct records matched for this row.

error

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_reason tells you what kind of fraud the contributing org saw (WPF first-party, WIF identity, WSF synthetic).

  • reported_on tells you how recent the contribution is.

  • matched_on tells you *which* identifier(s) triggered the hit — useful for distinguishing "shared phone with a known fraudster" from "exact TIN match."

  • amount and product_type give 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.