Get started › Overview
Overview
Screen people, companies and transactions against sanctions, PEP, watchlist and adverse-media data — from your own software or from the console.
AMLPRO is a screening platform. You send a name; it comes back with the records that match, the lists they came from, and a report you can keep. It covers sanctions and PEP screening, watchlist and enforcement checks, corporate registry data and adverse media, with matching rules you configure yourself.
For compliance teams, AMLPRO takes the guesswork out of a name check and leaves an audit trail behind every decision. For engineers, it is a signed REST API that answers in under a second and bills per search from a prepaid balance — no SDK, no session, no login step.
This documentation covers the whole platform: what the pieces are, how a screening runs, how to shape matching with the workflow builder, how billing and access work, and how to call the API.
To get up and running:
- Sign in to the console — every new account starts on the free starter package.
- Create an API credential and copy the secret key; it is shown once.
- Sign your first request and call
POST /ir-find. - Build a workflow so the results match your risk appetite.
- Top up the balance and go live.
Read How a screening runs before anything else. Those six steps explain most of what you will see coming back from the API.
The two ways in
There are two ways to reach the same screening engine, over the same data.
Called by your own software. Takes a name, returns the records that match it, charges the account and writes the log. Every request is signed.
Used by your compliance team. Search by hand, build workflows, read usage logs, hold API credentials, manage the plan, the balance and the team.
Both run the same six steps, charge the same rates and write to the same usage log, so a search run by hand and a search run from your code are indistinguishable afterwards except for who ran it.
How a screening runs
One call does six things, in this order.
The key names the credential; the HMAC proves you hold its secret.
Credential active, account active, IP allowed, permission held, entity type sold by the plan.
Every record whose name, alias or transliteration is close enough to the one you sent. The only stage that decides what the search can see.
Your own rules walk each candidate and keep it or drop it.
The plan's price for that entity type, taken from the prepaid balance.
The result, the name, the cost, who ran it, and a five-minute signed link to the PDF.
Workflow builder
A workflow is your own answer to which of these matches are really mine. It is a small graph: a chain of steps, each outcome wired to the next step or to one of two endings. Every candidate enters at the name step and walks until it reaches an ending.
The kinds of step
| Kind | What it is for |
|---|---|
name | The match itself. Always first, and cannot be removed. |
narrow | Compares an attribute of the subject you sent against the record. |
confirm | An exact identifier. A hit settles identity; a miss does nothing. |
score | Too sparse or messy to decide on, but useful for ordering. |
scope | A property of the record rather than of your subject, so it needs no input. |
What a step can test
The steps available depend on what is being screened.
Individual
| Step | Kind | What it does |
|---|---|---|
| Name | name |
Names, aliases and transliterations close to the one you send. |
| Date of birth | narrow |
Same birth year. Compared by year, not by exact date, so send whatever you hold. |
| Country | narrow |
One step over citizenship, nationality and where the person has held office. |
| Gender | score |
Demotes a record whose recorded gender differs. Never excludes. |
| Place of birth | score |
Nudges up similar-sounding places. The source mixes city, district and country, in local script. |
| Passport number | confirm |
An exact document-number match that settles identity outright. |
| National ID | confirm |
An exact national identity-number match. |
Company and organization
| Step | Kind | What it does |
|---|---|---|
| Name | name |
Registered name, trading names and former names. |
| Country of registration | narrow |
Where it was incorporated, not where it operates. |
| Date of incorporation | narrow |
Same year. Compared by year, not by exact date. |
| Registration or tax number | confirm |
Registration, tax, INN/OGRN or LEI. Not every number is unique to one company. |
| Owner or director risk | scope |
Keeps companies whose owner or director is sanctioned, even when the company is not. |
Adverse media takes the name step only.
Three rules, all of them fail-open
In screening, a missed match costs more than an extra one. Every rule resolves toward keeping the record.
- A step you sent no value for cannot be evaluated, so it is skipped. Sending no date of birth narrows nothing.
- A step whose field the record does not carry follows your no-value rule, which can only be carry on or include.
- An outcome wired to nothing keeps the record.
Every decision is recorded per candidate, so a record that survived because three steps were skipped is distinguishable from one that passed three checks.
Plans and balance
Screening is billed in money from a prepaid balance, per search, at the price the plan sets for that entity type.
Terms, not a subscription counter
Each period an account holds a package is its own term, with its own credit and its own rates. An account runs one package at a time; buying another ends the one running, and unspent credit tied to that term is forfeited while money never tied to it — opening balances, payments — stays with the customer. Prepaid credit is the exception: what is left on a prepaid plan moves to the balance when the plan changes, and is spent after the new plan's own credit, at the new plan's rates. Where terms overlap, the oldest one with credit left prices the next search.
What a plan sells
There are four services. A plan prices each one separately, and they are the same
four you ask for as schema on a search.
| Service | Ask for it as | Screens |
|---|---|---|
| Individual | Person |
A named person, against sanctions, PEP, watchlist and enforcement listings. |
| Company | Company |
A registered company. |
| Organization | Organization |
An organization that is not registered as a company. |
| Adverse media | Adverse Media |
Negative news coverage. |
Each of the four is set to one of three things on the plan:
| Setting | Meaning |
|---|---|
| Included | Covered by the price of the package. |
| Add-on | Usable, charged per search at the rate beside it. |
| Not available | Not sold. Refused with 403. |
A service is sold if any package you are running sells it, so a second package bought to add a service takes effect straight away.
Unlimited plans
A plan can sell the period instead of the searches. On one of those the fee buys the month and a search costs nothing: the balance is not read and not written, so there is no balance to run out and no 402 to hit. It still screens only what the plan sells, so an unlimited plan can be Individual-only, and a service set to not available is still refused.
The free starter
Every new account is put on a free package: Individual screening only, worth exactly 100 screenings a month, renewed monthly until the account buys something. It is granted, never sold, and has no subscription to cancel.
Volume pricing
One price list for the platform, shown on every customer's Packages page as a slider: at each monthly call volume, the price a month, the calls it includes and the rate per call. It sells nothing — its button opens a support ticket naming the volume chosen. What a search costs is still the plan's per-search price.
Roles and access
A company has one owner and any number of members. The owner always has every permission and cannot be locked out. A member gets a role.
| Can | Admin | Compliance | Developer | Viewer |
|---|---|---|---|---|
| Search and build workflows | Yes | Yes | Yes | No |
| Read usage logs and sources | Yes | Yes | Yes | Yes |
| Hold API credentials | Yes | No | Yes | No |
| See packages, balance, payments | Yes | No | No | No |
| Manage the team and roles | Yes | No | No | No |
| Change the company logo | Yes | No | No | No |
| Edit company details | No | No | No | No |
The last row is not an oversight. The company's legal details are the account holder's own record, so they stay the owner's whatever role a member holds. The logo is not: it is the mark on their invoices and at the top of their console, and the person who runs the account day to day is usually an Admin. A company can also define its own roles from the same set of permissions.
Not only in the menu. Hiding a link is not a permission — a member who types the URL of a page their role does not allow is refused by the server.
Authentication
Every request carries three headers. There is no session, no bearer token and no login step.
Every sample below uses https://api.example.com as a stand-in. The real
host is shown beside the credential when you create one in Dev
Credentials, together with the app key and the secret — substitute it there
and the samples run as written.
| Header | Value |
|---|---|
ikey | Your app key. Identifies the credential. |
inonce | The current Unix timestamp, in seconds, as a string. |
isignature | Base64 of an HMAC-SHA256, computed as below. |
The signature
Concatenate three values in this order, remove every space, and sign what is left with your secret key.
// concatenate, then delete all spaces
payload = <subject> + inonce + ikey
// sign with the secret key, then base64
signature = base64( HMAC-SHA256( secret_key, payload ) )
<subject> is whatever the call is about:
| Call | Subject to sign |
|---|---|
POST /ir-find | the full_name you are screening |
POST /ir-download | the log_id of the screening |
GET /download-log/<token> | an empty string |
Jane Doe signs as JaneDoe. Sign the name you
are sending with its spaces removed — not a trimmed, lowercased or otherwise
normalised version of it. A mismatch here is the most common cause of a refused
call: 400 on
/ir-find and
/ir-download,
401 on the
download link.
Signing code
The same request in four languages. Switching the tab switches every sample on the page.
import base64, hashlib, hmac, time, requests
APP_KEY = "your-app-key"
SECRET_KEY = "your-secret-key"
BASE = "https://api.example.com"
def sign(subject: str, ikey: str, secret: str, inonce: str) -> str:
payload = f"{subject}{inonce}{ikey}".replace(" ", "")
digest = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).digest()
return base64.b64encode(digest).decode()
name = "Jane Doe"
inonce = str(int(time.time()))
response = requests.post(
f"{BASE}/ir-find",
headers={
"ikey": APP_KEY,
"inonce": inonce,
"isignature": sign(name, APP_KEY, SECRET_KEY, inonce),
"Content-Type": "application/json",
},
json={
"full_name": name,
"schema": "Person",
"percent_limit": 80,
"result_limit": 10,
},
timeout=90,
)
print(response.status_code, response.json()["records_found"])
<?php
$appKey = 'your-app-key';
$secretKey = 'your-secret-key';
$base = 'https://api.example.com';
function amlpro_sign(string $subject, string $ikey, string $secret, string $inonce): string {
$payload = str_replace(' ', '', $subject . $inonce . $ikey);
return base64_encode(hash_hmac('sha256', $payload, $secret, true));
}
$name = 'Jane Doe';
$inonce = (string) time();
$body = json_encode([
'full_name' => $name,
'schema' => 'Person',
'percent_limit' => 80,
'result_limit' => 10,
]);
$ch = curl_init($base . '/ir-find');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 90,
CURLOPT_HTTPHEADER => [
'ikey: ' . $appKey,
'inonce: ' . $inonce,
'isignature: ' . amlpro_sign($name, $appKey, $secretKey, $inonce),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $body,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
echo $status . ' ' . $result['records_found'] . PHP_EOL;
import crypto from "node:crypto";
const APP_KEY = "your-app-key";
const SECRET_KEY = "your-secret-key";
const BASE = "https://api.example.com";
function sign(subject, ikey, secret, inonce) {
const payload = `${subject}${inonce}${ikey}`.replaceAll(" ", "");
return crypto.createHmac("sha256", secret).update(payload).digest("base64");
}
const name = "Jane Doe";
const inonce = String(Math.floor(Date.now() / 1000));
const response = await fetch(`${BASE}/ir-find`, {
method: "POST",
headers: {
ikey: APP_KEY,
inonce,
isignature: sign(name, APP_KEY, SECRET_KEY, inonce),
"Content-Type": "application/json",
},
body: JSON.stringify({
full_name: name,
schema: "Person",
percent_limit: 80,
result_limit: 10,
}),
});
const result = await response.json();
console.log(response.status, result.records_found);
# the signature is HMAC-SHA256 over "JaneDoe" + inonce + ikey
# compute it in your own code — this shows only the request shape
curl --request POST 'https://api.example.com/ir-find' \
--header 'ikey: your-app-key' \
--header 'inonce: 1790640000' \
--header 'isignature: BASE64_HMAC_SHA256_HERE=' \
--header 'Content-Type: application/json' \
--data '{
"full_name": "Jane Doe",
"schema": "Person",
"percent_limit": 80,
"result_limit": 10
}'
The secret key is shown once, when the credential is created, and cannot be read back afterwards. Anything holding it can screen at your expense.
Credential permissions
Each credential carries its own permissions and, optionally, its own IP allowlist. A call outside them is refused even with a valid signature. These are the names you tick when you create a credential in the console.
| Permission | Allows |
|---|---|
| Name Screening | Screen a name and get the matching records back — POST /ir-find |
| Report Download | The PDF for a screening that has already run, by its reference — POST /ir-download |
| Report Download Link | The PDF from the time-limited link a screening hands back — GET /download-log/<token> |
Run a screening
Requires Name Screening. Accepts JSON or form-encoded fields. Sign the
full_name.
Body params
The name to screen. This is also the signature subject.
Which of the four services to screen: Person,
Company, Organization, Adverse Media, or
all for every one. Comma-separate to combine them —
Person, Adverse Media. Omit it and only
Person is screened. A value that is not one of these is
refused with 400 rather than quietly screened as
a person.
Case does not matter, and Individual, Org,
Organisation and ADM are accepted as aliases. The
field itself is also read from entity_type,
entity_types, type, search_type or
entity.
Minimum match score, 0–100. Lower it to see more candidates.
Maximum records returned, 1–1000.
full for records, count for the totals only.
Your own reference. Stored on the log and returned unchanged.
Workflow inputs
Optional. Each is what a workflow step compares against; send nothing and that step is skipped.
| Field | Also read from |
|---|---|
dob | date_of_birth, birth_date |
country | nationality, citizenship |
gender | sex |
birth_place | place_of_birth |
passport | passport_number |
national_id | id_number |
jurisdiction | country_of_registration |
incorporation_date | incorporated |
identifier | registration_number, tax_number, lei |
download_url is signed and expires five minutes after the search. Call
it straight away, or use /ir-download later —
that one works at any time, from the log_id.
Download a report by reference
Requires Report Download. Answers with application/pdf.
The log_id returned by the screening. Sign this same value as the
subject — not the name.
Works at any time after the screening, for as long as the log is retained. This is the call to use for a report you fetch on a schedule, or re-fetch months later for an audit.
Download a report by link
Requires Report Download Link. Uses the download_url a screening
returned, which expires five minutes after the search.
Already present in download_url. Use that URL as returned.
Sign an empty subject: the payload is
inonce + ikey with the spaces removed.
It carries no authority on its own; the same three headers are still required. A URL that authenticated by itself would hand a full screening report to anything it was forwarded to — a mail thread, a log aggregator, a proxy that records paths.
The record shape
Results arrive under result.AML and result.adverse_media. One
record looks like this — the values are made up, the shape is not.
{
"entity": {
"schema": "Person",
"name": "Jane Doe",
"sources": [
"US OFAC Specially Designated Nationals (SDN) List",
"US Trade Consolidated Screening List (CSL)",
"US SAM Procurement Exclusions"
]
},
"properties": { "name": ["DOE, Jane", "Jane Doe"] },
"topics": ["sanction", "debarment", "export.control"],
"person_details": { "first_name": "Jane", "last_name": "DOE" },
"relationships": {
"sanctions": [
{
"sanctioned_entity_name": "Jane Doe",
"authority": "Office of Foreign Assets Control",
"program": "EXAMPLE-01",
"provisions": "Block | EXAMPLE-01",
"listing_date": "Mon, 05 Feb 2024 00:00:00 GMT",
"country": "us",
"source_url": "https://www.treasury.gov/…"
}
]
},
"similarity_percentage": "100.00",
"matched_name": { "value": "Jane Doe", "type": "name" },
"category": "AML"
}
| Field | What it is |
|---|---|
entity.name | The record's own name. |
entity.sources | The lists the record was found on, by name. |
matched_name | Which of the record's names your query matched, and where it came from — name, alias or weakAlias. |
similarity_percentage | The match score, as a string to two decimal places. |
topics | Why the record is listed: sanction, debarment, role.pep, crime. |
relationships | Sanctions, directorships, ownerships, family, associates, positions. Present only when the record carries them. |
category | AML or ADM. |
Internal identifiers are stripped from every response — each sits beside something readable that means the same thing. Empty fields are removed rather than returned as nulls, so the absence of a key means the record does not carry it.
Status codes
A refusal from /ir-find answers with
status: "failed" and a description that says what to do about
it. The download routes are older and answer some refusals with
status: "error" and message instead, so read both keys if you
are handling all three endpoints in one place.
| Code | Means | What to do |
|---|---|---|
| 200 | The search ran and was charged. | Read records_found; zero is a valid, billed answer. |
| 400 | A field is missing or out of range, the signature did not verify, the timestamp is stale, the account is inactive, or the calling IP is not on the credential's allow-list. | Read description; it names the field, or the address that was
refused. A signature failure says no more than that, by design — check the
space rule and that the timestamp is current. |
| 401 | Only from GET /download-log: its signature
did not verify, or one of the three headers is missing. |
That call signs an empty string — the token is in the URL,
not in the payload. /ir-find refuses a bad
signature with 400 instead. |
| 402 | Not enough balance for this search, or no plan on the account at all. | Top up. The body carries required, balance,
currency and a breakdown of what was being charged,
so a client can say what is short without parsing the sentence. |
| 403 | The plan does not sell this entity type, or the credential was not issued with the permission this call needs. | Read description; it names what was refused. An IP that is not
on the allow-list is a 400, not this. |
| 429 | More than 120 requests a minute on one credential. | Wait the retry_after seconds in the body, then carry on. |
| 500 | Something failed on our side. | Retry; if it persists, quote the log_id if you have one. |
An account that cannot screen a Company is told so, rather than answered with an empty
list that reads as "no matches". Treat an empty result on a
200 as a real, billed clean search.
Every screening is logged with what was searched, what matched, which list it came from, what it cost and who ran it. That record is what makes an answer defensible six months later — and it is why a screening log is never deleted.