Start KYB process

A KYB process on Dotfile is a case: one company, the companies and people attached to it,
and the checks that verify them. Creating all of it takes one API call. What follows is
collecting what you do not have yet, and receiving the results.

This guide assumes you have an API key and a template. Every request carries your key in the
X-DOTFILE-API-KEY header, against https://api.dotfile.com/v1. See
Authentication.

One POST to /cases carries the companies, the individuals and the template key; the case then holds the main company, an affiliated company and an individual, with the checks the template created on companies and on people, and results return asynchronously as webhooks

1 — Identify the company

Skip this step if you already hold the company's country and registration number: pass them
straight to the case and Dotfile fetches the rest.

Otherwise, ask your user for a country and a name, and call
Search companies:

curl -G https://api.dotfile.com/v1/company-data/search \
  -H "X-DOTFILE-API-KEY: $DOTFILE_API_KEY" \
  -d country=FR -d name="Dotfile"

Search returns several candidates, and that is not a defect. Two companies can share a
name, in the same country, in the same registry. Nothing downstream can pick the right one for
you — so either your user selects from the list, or you match on a registration number you
already trust. Never take the first result blindly: you would be onboarding a company your
customer never named.

Each result carries a search_ref. Fetch company data turns it into
the full profile — legal form, address, classifications, shareholders and officers:

curl https://api.dotfile.com/v1/company-data/fetch/$SEARCH_REF \
  -H "X-DOTFILE-API-KEY: $DOTFILE_API_KEY"

Countries covered by your workspace's providers are listed by
List countries.

2 — Create the case, its entities and its checks in one call

Create a case accepts the whole graph in its body: the main company, its
affiliated companies, the individuals, and the relations between them. Entities reference each
other inside the same payload through ref — no identifier exists yet, so you invent your own
and Dotfile resolves them.

curl -X POST https://api.dotfile.com/v1/cases \
  -H "X-DOTFILE-API-KEY: $DOTFILE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp onboarding",
    "external_id": "customer_8412",
    "template_key": "kyb_standard",
    "companies": [
      {
        "ref": "main-co",
        "type": "main",
        "name": "Acme Corp",
        "country": "FR",
        "registration_number": "552100554"
      },
      {
        "ref": "holding",
        "type": "affiliated",
        "name": "Acme Holding",
        "country": "LU",
        "registration_number": "B123456",
        "relations": [
          {
            "source_company_ref": "main-co",
            "roles": ["shareholder"],
            "ownership_percentage": 80
          }
        ]
      }
    ],
    "individuals": [
      {
        "ref": "ceo",
        "first_name": "Jane",
        "last_name": "Doe",
        "email": "[email protected]",
        "birth_date": "1980-04-12",
        "is_business_contact": true,
        "is_beneficial_owner": true,
        "relations": [
          {
            "source_company_ref": "main-co",
            "roles": ["legal_representative", "shareholder"],
            "ownership_percentage": 20,
            "position": "CEO"
          }
        ]
      }
    ]
  }'

Four things worth knowing about this payload:

  • template_key creates the checks. The template decides which checks run on the company
    and on each individual, based on the rules you configured. You do not create checks one by
    one — see Templates.
  • roles describes a relation, and takes legal_representative or shareholder. Being a
    beneficial owner is a property of the person, not of the relation: it is the
    is_beneficial_owner flag, alongside is_controlling_person, is_signatory, is_delegator
    and is_business_contact.
  • source_company_ref names the company at the other end of the relation. Omit it and the
    case's main company is used, which is what you want most of the time.
  • external_id is your own identifier, unique per workspace. It also works in place of the
    case id when reading a case back, so you never have to store ours.

The response is the complete case: its id, and every company and individual with the ids
Dotfile assigned them. No follow-up call is needed to learn what was created.

Coming from the older four-call sequence?

Creating the case first, then posting each company and individual against its id, still works
and is not deprecated — Create a company and
Create an individual take a case_id. Nested creation is simply
fewer round-trips and fewer partial failures: the whole graph is written in one transaction,
or none of it is.

3 — Collect what the registry does not hold

Registries give you the structure. They do not give you a proof of address, a signed document,
or a selfie. That comes from your customer, through the
Client Portal — either the hosted one or your own
front-end feeding the API.

To hand a customer their portal, generate an authenticated link:

curl -X POST https://api.dotfile.com/v1/cases/$CASE_ID/share-client-portal-link \
  -H "X-DOTFILE-API-KEY: $DOTFILE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"business_contact_id": "'$INDIVIDUAL_ID'"}'

The individual must belong to the case, be relevant, and have an email address; the client
portal must be online. Marking someone is_business_contact at creation, as in the payload
above, is what makes them eligible.

4 — Receive the results

Checks are asynchronous — a screening takes seconds, a document review can take a person. Two
ways to follow them, and you want the first:

  • Webhooks. Subscribe to Check.Started, Check.Approved, Check.Rejected,
    Check.ReviewNeeded, Check.Expired, and to Case.RiskUpdated and Case.StatusUpdated for
    the case itself. Full list and payloads: Webhooks.
  • Read the case. Retrieve a case returns companies and individuals
    with their checks embedded, so one GET gives you the current state of the whole process —
    by case id or by your external_id.

Check.ReviewNeeded is the event that matters operationally: it means a provider could not
decide on its own — a blurred document, a name that partially matches a sanctions list — and a
judgement is needed.

That judgement can be recorded either way. In the console, where the document, the extracted
fields and the screening hits are laid out for the reviewer. Or by API, on the check's own path:

curl -X PATCH https://api.dotfile.com/v1/checks/document/$CHECK_ID/review \
  -H "X-DOTFILE-API-KEY: $DOTFILE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action": "approve", "comment": "Certificate matches the registry extract"}'

action is approve or reject, and override: true changes a decision already made. AML hits
can be reviewed one by one through checks/aml/{id}/hits/review. Company monitoring is the
exception: it is read-only over the API.

5 — Decide

When the checks are in, close the case with a review:

curl -X POST https://api.dotfile.com/v1/cases/$CASE_ID/reviews \
  -H "X-DOTFILE-API-KEY: $DOTFILE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "approved", "comment": "All checks cleared"}'

comment may be mandatory depending on your workspace settings, and next_review_at is
computed from your settings and the case's risk level unless you override it — which is how
periodic review gets scheduled.

A template with auto-approval enabled does this for you once every check it created is
approved. Either way, Case.ReviewConfirmed tells your system the process is over.

Next