Screen a shipment with one API call
Send us the air waybill you already have. We screen every party and every goods line against sanctions, denied-party and export-control lists, verify the trading parties, and return one structured result.
Overview
One request covers up to four checks. Your account has a default set; you can choose per request.
| Check | What it screens | Typical time |
|---|---|---|
denied_party | Every party (shipper, consignee, agent, notify) against international sanctions lists, with politically exposed persons and enforcement lists where enabled. | 1–3 s |
trade_regulation | Every goods line by HS code against the dual-use, military and sanctioned-goods control lists of the export country, with sanctions targeted at the destination country. | 1–3 s |
kyc_ai | Shipper and consignee: AI-assisted verification against public sources. Does the company exist, does it match the declared name and address, is it active? Every finding comes with its sources. | 30–90 s |
company_report | Optional add-on. Registered company data: status, legal form, incorporation date, employees, credit rating, recommended credit limit, shareholders. | 2–5 s |
Quick start
Authenticate with your API key as a bearer token. Sandbox keys start with sk_test_ and never call a live source or generate charges. Live keys start with sk_live_. Keep keys on your server.
curl https://sandbox.qcargoapi.com/v1/account \ -H "Authorization: Bearer sk_test_..."
curl -X POST "https://sandbox.qcargoapi.com/v1/screenings?wait=20" \ -H "Authorization: Bearer sk_test_..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 7d0f4a52-2f7e-4c1e-9a43-6b3c8f1d2e90" \ -d @screening.json
How a screening runs
Denied-party and trade-regulation results are back within seconds. The AI verification researches public sources and takes longer. That is why a screening is asynchronous, and why you can read the fast results before the slow one is done.
GET /screenings/{id} shows progress.Prefer a single blocking call? Add ?wait= (up to 30 seconds). If everything finishes in time you get 200 OK with the final result; otherwise 202 with what is done so far, and the webhook follows.
What you send
Send the raw Cargo-IMP messages exactly as you have them. We read FWB (versions 16 and 17) and FHL (versions 4 and 5).
shipment.houses, and every party and goods line carries the house_id it belongs to.{
"reference": "CAI-BKG-2026-118842",
"checks": [
"denied_party",
"trade_regulation",
"kyc_ai"
],
"input": {
"format": "cargo_imp",
"fwb": "FWB/16\n235-12345675AMSIST/T3K612.0MC3.10\nFLT/TK1958/14\nRTG/ISTTK\nSHP\nNAM/TULIPA PRECISION METALS BV\nADR/INDUSTRIEWEG 12\nLOC/EINDHOVEN\n/NL/5651GK/TE/31401234567\nCNE\nNAM/BOSPORUS TEKNIK MAKINA AS\nADR/ORGANIZE SANAYI BOLGESI 4 CAD NO 7\nLOC/ISTANBUL\n/TR/34906\nAGT//3747012/0001\n/NORTHDYKE FORWARDING BV\n/AMSTERDAM\nCVD/EUR//PP/NVD/NCV/XXX\nRTD/1/P2/K480.0/CQ/W480.0/R2.15/T1032.00\n/NG/MARAGING STEEL BARS\n/2/NH/72281050\nRTD/2/P1/K132.0/CQ/W132.0/R2.15/T283.80\n/NG/STEEL RETAINING RINGS\n/2/NH/73182900\nISU/03NOV26/AMSTERDAM"
}
}All companies, addresses and numbers in this guide are fictional.
Request fields
| Field | Description | |
|---|---|---|
reference | required | Your own reference (booking or quote id). Returned unchanged and searchable. |
checks | optional | Which checks to run. Omit to use your account defaults. |
input.format | required | cargo_imp for raw messages, or json for structured data (see the OpenAPI spec, ShipmentInput). |
input.fwb | required for cargo_imp | The FWB message text, line breaks preserved. |
input.fhl | optional | Array of FHL message texts, one per house waybill. |
metadata | optional | Up to 20 key/value strings, stored and returned unchanged. |
What you get back
The screening object for the example above, once completed. The same object comes back from POST, from GET /screenings/{id} and in the webhook.
{
"id": "scr_01JC4T8Z6Q2W9V3M7K5N0P1R2S",
"object": "screening",
"reference": "CAI-BKG-2026-118842",
"environment": "sandbox",
"status": "completed",
"outcome": "review",
"created_at": "2026-11-03T09:14:02Z",
"completed_at": "2026-11-03T09:14:51Z",
"metadata": {},
"shipment": {
"source": "fwb",
"awb": "235-12345675",
"houses": [],
"origin": {
"airport": "AMS",
"country": "NL"
},
"destination": {
"airport": "IST",
"country": "TR"
},
"export_country": "NL",
"flights": [
{
"flight": "TK1958",
"date": "2026-11-14"
}
],
"parties": [
{
"id": "pty_1",
"house_id": null,
"role": "shipper",
"name": "TULIPA PRECISION METALS BV",
"type": "organisation",
"address": {
"street": "INDUSTRIEWEG 12",
"city": "EINDHOVEN",
"postal_code": "5651GK",
"country": "NL"
}
},
{
"id": "pty_2",
"house_id": null,
"role": "consignee",
"name": "BOSPORUS TEKNIK MAKINA AS",
"type": "organisation",
"address": {
"street": "ORGANIZE SANAYI BOLGESI 4 CAD NO 7",
"city": "ISTANBUL",
"postal_code": "34906",
"country": "TR"
}
},
{
"id": "pty_3",
"house_id": null,
"role": "agent",
"name": "NORTHDYKE FORWARDING BV",
"type": "organisation",
"address": {
"city": "AMSTERDAM",
"country": "NL"
}
}
],
"goods": [
{
"id": "gds_1",
"house_id": null,
"description": "MARAGING STEEL BARS",
"hs_code": "72281050",
"pieces": 2,
"gross_weight_kg": 480.0
},
{
"id": "gds_2",
"house_id": null,
"description": "STEEL RETAINING RINGS",
"hs_code": "73182900",
"pieces": 1,
"gross_weight_kg": 132.0
}
]
},
"warnings": [
{
"code": "party_address_incomplete",
"message": "Agent has no street address; screened on name, city and country.",
"path": "parties[2].address"
}
],
"checks": {
"denied_party": {
"status": "completed",
"outcome": "clear",
"completed_at": "2026-11-03T09:14:04Z",
"error": null,
"results": [
{
"party_id": "pty_1",
"outcome": "clear",
"matches": []
},
{
"party_id": "pty_2",
"outcome": "clear",
"matches": []
},
{
"party_id": "pty_3",
"outcome": "clear",
"matches": []
}
]
},
"trade_regulation": {
"status": "completed",
"outcome": "review",
"completed_at": "2026-11-03T09:14:05Z",
"error": null,
"jurisdiction": "EU",
"sanctions_target": "TR",
"results": [
{
"goods_id": "gds_1",
"hs_code": "72281050",
"outcome": "review",
"regimes": [
{
"regime": "dual_use",
"result": "match"
},
{
"regime": "military",
"result": "no_match"
},
{
"regime": "sanctions",
"result": "not_applicable"
}
],
"controlled_items": [
{
"regime": "dual_use",
"control_number": "1C216",
"hs_code": "72281050",
"category": "1 - Special materials and related equipment / C - Materials",
"description": "Maraging steel, other than that specified in 1C116, 'capable of' an ultimate tensile strength of 1 950 MPa or more, at 293 K (20 °C). Note: 1C216 does not control forms in which all linear dimensions are 75 mm or less. Technical note: the phrase maraging steel 'capable of' encompasses maraging steel before or after heat treatment.",
"control_list": {
"name": "Regulation (EU) 2021/821 — Union regime for the control of exports, brokering, technical assistance, transit and transfer of dual-use items",
"reference": "02021R0821-20251115",
"publisher": "European Union",
"url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:02021R0821-20251115"
},
"note": "The match is a correlation via the HS code. The goods fall under 1C216 only if they meet its technical specification; an export licence is then required."
}
]
},
{
"goods_id": "gds_2",
"hs_code": "73182900",
"outcome": "clear",
"regimes": [
{
"regime": "dual_use",
"result": "no_match"
},
{
"regime": "military",
"result": "no_match"
},
{
"regime": "sanctions",
"result": "not_applicable"
}
],
"controlled_items": []
}
]
},
"kyc_ai": {
"status": "completed",
"outcome": "review",
"completed_at": "2026-11-03T09:14:51Z",
"error": null,
"results": [
{
"party_id": "pty_1",
"outcome": "clear",
"confidence": 96,
"summary": "Registered Dutch private company at the declared address, active, manufacturer of precision metal parts.",
"findings": [
{
"field": "name",
"declared": "TULIPA PRECISION METALS BV",
"found": "Tulipa Precision Metals B.V.",
"verdict": "confirmed",
"confidence": 98,
"explanation": "Company register lists the same legal name; capitalisation and punctuation differ only.",
"sources": [
"https://register.example/nl/tulipa-precision-metals"
]
},
{
"field": "street",
"declared": "INDUSTRIEWEG 12",
"found": "Industrieweg 12",
"verdict": "confirmed",
"confidence": 95,
"explanation": "Registered office address matches.",
"sources": [
"https://register.example/nl/tulipa-precision-metals"
]
},
{
"field": "activity_status",
"declared": null,
"found": "active",
"verdict": "confirmed",
"confidence": 94,
"explanation": "Registration is active; no dissolution or bankruptcy notice found.",
"sources": [
"https://register.example/nl/tulipa-precision-metals"
]
}
]
},
{
"party_id": "pty_2",
"outcome": "review",
"confidence": 71,
"summary": "Company found in Istanbul with a matching name, but the registered postal code differs from the declared one.",
"findings": [
{
"field": "name",
"declared": "BOSPORUS TEKNIK MAKINA AS",
"found": "Bosporus Teknik Makina A.Ş.",
"verdict": "confirmed",
"confidence": 93,
"explanation": "Trade registry gazette lists the company under this name.",
"sources": [
"https://gazette.example/tr/bosporus-teknik"
]
},
{
"field": "postal_code",
"declared": "34906",
"found": "34953",
"verdict": "differs",
"confidence": 80,
"explanation": "The trade registry and the company website both give 34953 (Tuzla organised industrial zone).",
"sources": [
"https://gazette.example/tr/bosporus-teknik",
"https://bosporus-teknik.example/contact"
]
}
]
}
]
}
},
"usage": {
"billable_checks": 3,
"items": [
{
"check": "denied_party",
"units": 3
},
{
"check": "trade_regulation",
"units": 4
},
{
"check": "kyc_ai",
"units": 2
}
]
}
}While the AI verification is still running, the same call returns this:
{
"id": "scr_01JC4T8Z6Q2W9V3M7K5N0P1R2S",
"object": "screening",
"reference": "CAI-BKG-2026-118842",
"environment": "sandbox",
"status": "processing",
"outcome": null,
"created_at": "2026-11-03T09:14:02Z",
"completed_at": null,
"checks": {
"denied_party": {
"status": "completed",
"outcome": "clear",
"completed_at": "2026-11-03T09:14:04Z",
"error": null
},
"trade_regulation": {
"status": "completed",
"outcome": "review",
"completed_at": "2026-11-03T09:14:05Z",
"error": null
},
"kyc_ai": {
"status": "processing",
"outcome": null,
"completed_at": null,
"error": null
}
}
}Reading the result
Start with the top-level outcome. It is the most severe outcome of all checks.
Clear clear
Nothing found that needs attention. The shipment can proceed as far as these checks are concerned.
Review review
At least one possible match or controlled item. A person should look before the shipment proceeds. The check results say exactly what and why.
Incomplete incomplete
A check, or part of one, could not complete (its error says why). Nothing was found in what did complete. Failed parts are not billed. To try again, create a new screening with a new Idempotency-Key, optionally with retry_of pointing to this one.
Warnings
warnings[] flag gaps in the message itself, such as a missing HS code or an incomplete address. They do not stop the screening, but they do limit what can be checked.
In the example
- shipment shows how we read your FWB: three parties, two goods lines, export from NL to TR. Use it to confirm we interpreted the message as intended.
- denied_party is clear: none of the three parties appears on a list.
- trade_regulation is review: HS 7228.10.50 (maraging steel) correlates with EU dual-use item 1C216. This is a correlation through the HS code. The bars are controlled only if they meet the technical specification, and an export licence is then required. The retaining rings are clear.
- kyc_ai is review: the shipper is confirmed with 96% confidence. The consignee exists, but its registered postal code differs from the declared one. Every finding lists the sources it was checked against.
Status values
| Level | Values |
|---|---|
Screening status | processing, completed |
Check status | queued, processing, completed, failed, skipped (the input lacked what the check needs, such as an HS code) |
Trade-regulation regimes[].result | match, no_match, not_applicable (no such regime for this jurisdiction or destination) |
KYC findings[].verdict | confirmed, differs, not_found |
Webhooks
We deliver screening.completed to your webhook URL once every requested check is final. The body is { id, type, created_at, data }, where data is the full screening object.
- Respond with any
2xxwithin 10 seconds. We retry failed deliveries with exponential backoff for up to 24 hours. - A delivery can arrive more than once. Deduplicate on the event
id. - Verify every delivery with the
Screening-Signatureheader before trusting it. - Every delivery is sent with
User-Agent: Qcargo-Screening/1.
import crypto from "node:crypto";
// header: "t=1793710800,v1=5f2b8c..." rawBody: the request body exactly as received
export function verify(header, rawBody, secret) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; // older than 5 minutes
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Errors, retries, idempotency
Every POST /screenings needs an Idempotency-Key. Use one per screening you mean to create, such as a UUID or your booking id plus a revision. If a request times out or you get a 5xx, retry with the same key. You get the original screening back, and it is never created or billed twice.
| HTTP | error.code | Meaning |
|---|---|---|
| 400 | invalid_request | Missing or malformed field; param names it. |
| 401 | unauthenticated | Missing or unknown API key. |
| 403 | check_not_enabled, account_inactive | The key is valid but not allowed to do this. |
| 409 | idempotency_conflict | The key was already used with a different body. |
| 422 | no_screenable_content | The message parsed, but has nothing to screen (for example no parties). Not billed. |
| 429 | rate_limited | Slow down; wait the Retry-After seconds. |
| 5xx | server_error | Our side. Safe to retry with the same Idempotency-Key. |
Errors always have the shape {"error": {"code", "message", "param", "request_id"}}. Quote the request_id when you contact us.
Sandbox
Sandbox results are deterministic, so you can test every branch of your integration:
| Put this in your request | You get |
|---|---|
Party name containing SANDBOX DENIED | A denied-party match on that party |
HS code starting with 7228 | Dual-use item 1C216, as in the example. Any other HS code gives no match. |
Party name containing SANDBOX UNKNOWN | KYC findings not_found, outcome review |
Party name containing SANDBOX TIMEOUT | The checks for that party fail with source_timeout, and the screening outcome is incomplete |
What is billed
Only checks that complete are billed. Failed and skipped checks, 422 responses and idempotent retries are not billed. The usage block on every screening shows what it counts towards your monthly invoice. Units follow the screening requests actually made: one per party for denied_party, one per goods line per control regime for trade_regulation, and one per verified party for kyc_ai and company_report. One waybill can therefore count several units; the example above counts 3 + 4 + 2. Failed parts are not counted. Your contract is leading.
OpenAPI specification
The full contract, with every field, enum and error, is available as OpenAPI 3.1. Load it into Swagger UI, Postman or your code generator.
openapi: 3.1.0
info:
title: Qcargo Screening API
version: 1.0.0-draft.1
summary: Screen air cargo shipments and trading parties in one call.
license:
name: Proprietary, provided under the client's service agreement
description: |
Send a shipment (a Cargo-IMP FWB/FHL message, or structured JSON) and receive one consolidated
screening result: denied-party screening of every party, trade-regulation screening of every goods
line (dual-use, military and sanctioned goods), and AI-assisted verification of the trading parties.
**Draft for integration planning.** The product name and some field details are
provisional and may change before the first live release. Changes will be announced and
reflected in an updated version of this document.
### Conventions
* All requests and responses are JSON (`Content-Type: application/json`), UTF-8.
* Timestamps are ISO 8601 in UTC. Country codes are ISO 3166-1 alpha-2. Airport codes are IATA.
* Unknown fields in responses must be ignored by the client: new fields are added without a version change.
* Identifiers are opaque strings with a type prefix (`scr_` screening, `pty_` party, `gds_` goods line, `evt_` event).
### Sandbox test triggers
Sandbox keys never call a live source. Results are deterministic so you can test every branch:
* a party name containing `SANDBOX DENIED` returns a denied-party match;
* an HS code starting with `7228` returns the dual-use item 1C216, any other code returns no match;
* a party name containing `SANDBOX UNKNOWN` returns a KYC result with `not_found` findings and outcome `review`;
* a party name containing `SANDBOX TIMEOUT` makes the checks for that party fail with `source_timeout`.
servers:
- url: https://sandbox.qcargoapi.com/v1
description: Sandbox. Test keys only, deterministic results, never billed.
- url: https://qcargoapi.com/v1
description: Production. Live keys only.
security:
- apiKey: []
tags:
- name: Screenings
description: Submit shipments and retrieve screening results.
- name: Account
description: Your account configuration.
- name: Webhooks
description: Events we send to your endpoint.
paths:
/screenings:
post:
tags: [Screenings]
operationId: createScreening
summary: Create a screening
description: |
Submits a shipment for screening. The response is the screening object.
Denied-party and trade-regulation checks typically finish within a few seconds. The AI party
verification researches public sources and can take 30–90 seconds. Screening is therefore
asynchronous:
* By default the call returns immediately with `202 Accepted` and `status: processing`.
* Pass `wait` (seconds, max 30) to hold the connection until the screening completes or the wait
expires. If everything finished in time you get `200 OK` with `status: completed`; otherwise
`202` with whatever checks already completed.
* Get the final result with `GET /screenings/{id}` or, preferably, a `screening.completed` webhook.
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
- name: wait
in: query
required: false
description: Seconds to wait for completion before responding (0–30). Default 0.
schema: { type: integer, minimum: 0, maximum: 30, default: 0 }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ScreeningRequest' }
examples:
cargoImp:
summary: Cargo-IMP FWB message
value:
reference: CAI-BKG-2026-118842
checks: [denied_party, trade_regulation, kyc_ai]
input:
format: cargo_imp
fwb: |-
FWB/16
235-12345675AMSIST/T3K612.0MC3.10
FLT/TK1958/14
RTG/ISTTK
SHP
NAM/TULIPA PRECISION METALS BV
ADR/INDUSTRIEWEG 12
LOC/EINDHOVEN
/NL/5651GK/TE/31401234567
CNE
NAM/BOSPORUS TEKNIK MAKINA AS
ADR/ORGANIZE SANAYI BOLGESI 4 CAD NO 7
LOC/ISTANBUL
/TR/34906
AGT//3747012/0001
/NORTHDYKE FORWARDING BV
/AMSTERDAM
CVD/EUR//PP/NVD/NCV/XXX
RTD/1/P2/K480.0/CQ/W480.0/R2.15/T1032.00
/NG/MARAGING STEEL BARS
/2/NH/72281050
RTD/2/P1/K132.0/CQ/W132.0/R2.15/T283.80
/NG/STEEL RETAINING RINGS
/2/NH/73182900
ISU/03NOV26/AMSTERDAM
json:
summary: Structured JSON
value:
reference: CAI-BKG-2026-118842
checks: [denied_party, trade_regulation]
input:
format: json
shipment:
awb: 235-12345675
origin: { airport: AMS, country: NL }
destination: { airport: IST, country: TR }
flights: [{ flight: TK1958, date: '2026-11-14' }]
parties:
- role: shipper
name: TULIPA PRECISION METALS BV
address: { street: INDUSTRIEWEG 12, city: EINDHOVEN, postal_code: 5651GK, country: NL }
- role: consignee
name: BOSPORUS TEKNIK MAKINA AS
address: { street: ORGANIZE SANAYI BOLGESI 4 CAD NO 7, city: ISTANBUL, postal_code: '34906', country: TR }
goods:
- description: MARAGING STEEL BARS
hs_code: '72281050'
pieces: 2
gross_weight_kg: 480.0
responses:
'200':
description: Screening completed within the requested wait.
content:
application/json:
schema: { $ref: '#/components/schemas/Screening' }
examples:
completed: { $ref: '#/components/examples/ScreeningCompleted' }
'202':
description: Screening accepted and still processing.
content:
application/json:
schema: { $ref: '#/components/schemas/Screening' }
examples:
processing: { $ref: '#/components/examples/ScreeningProcessing' }
'400': { $ref: '#/components/responses/InvalidRequest' }
'401': { $ref: '#/components/responses/Unauthenticated' }
'403': { $ref: '#/components/responses/Forbidden' }
'409': { $ref: '#/components/responses/IdempotencyConflict' }
'422': { $ref: '#/components/responses/UnprocessableMessage' }
'429': { $ref: '#/components/responses/RateLimited' }
'500': { $ref: '#/components/responses/ServerError' }
get:
tags: [Screenings]
operationId: listScreenings
summary: List screenings
description: Returns your screenings, newest first, with cursor pagination.
parameters:
- { name: reference, in: query, schema: { type: string }, description: Filter on your own reference. }
- { name: awb, in: query, schema: { type: string }, description: 'Filter on AWB number, e.g. 235-12345675.' }
- { name: status, in: query, schema: { $ref: '#/components/schemas/ScreeningStatus' } }
- { name: outcome, in: query, schema: { $ref: '#/components/schemas/Outcome' } }
- { name: created_after, in: query, schema: { type: string, format: date-time } }
- { name: created_before, in: query, schema: { type: string, format: date-time } }
- { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 } }
- { name: cursor, in: query, schema: { type: string }, description: '`next_cursor` from the previous page.' }
responses:
'200':
description: A page of screenings.
content:
application/json:
schema:
type: object
required: [data, has_more]
properties:
data: { type: array, items: { $ref: '#/components/schemas/Screening' } }
has_more: { type: boolean }
next_cursor: { type: [string, 'null'] }
'401': { $ref: '#/components/responses/Unauthenticated' }
/screenings/{id}:
get:
tags: [Screenings]
operationId: getScreening
summary: Retrieve a screening
parameters:
- { name: id, in: path, required: true, schema: { type: string }, example: scr_01JC4T8Z6Q2W9V3M7K5N0P1R2S }
responses:
'200':
description: The screening.
content:
application/json:
schema: { $ref: '#/components/schemas/Screening' }
examples:
completed: { $ref: '#/components/examples/ScreeningCompleted' }
'401': { $ref: '#/components/responses/Unauthenticated' }
'404': { $ref: '#/components/responses/NotFound' }
/account:
get:
tags: [Account]
operationId: getAccount
summary: Retrieve your account
description: The checks enabled for your account and the environment of the key you used. Useful as a connectivity test.
responses:
'200':
description: Your account.
content:
application/json:
schema: { $ref: '#/components/schemas/Account' }
example:
object: account
id: acc_cargoai
name: Cargo.AI
environment: sandbox
enabled_checks: [denied_party, trade_regulation, kyc_ai, company_report]
default_checks: [denied_party, trade_regulation, kyc_ai]
input_formats: [cargo_imp, json]
webhook_url: https://hooks.cargoai.example/screening
'401': { $ref: '#/components/responses/Unauthenticated' }
webhooks:
screening.completed:
post:
tags: [Webhooks]
operationId: screeningCompleted
summary: A screening finished
description: |
Sent once every requested check has reached a final state (`completed`, `failed` or `skipped`).
The payload carries the full screening object.
**Verifying the signature.** Every delivery has a `Screening-Signature` header:
`t=<unix timestamp>,v1=<hex HMAC-SHA256>`. Compute HMAC-SHA256 over `"<t>.<raw request body>"`
with your webhook signing secret and compare it to `v1` in constant time. Reject deliveries whose
timestamp is more than 5 minutes old.
**Delivery.** Respond with any 2xx within 10 seconds. Failed deliveries are retried with
exponential backoff for up to 24 hours. Deliveries can arrive more than once: deduplicate on `id`.
Every delivery is sent with the header `User-Agent: Qcargo-Screening/1`.
parameters:
- name: Screening-Signature
in: header
required: true
schema: { type: string }
example: t=1793710800,v1=5f2b8c0e6a7d4b1f9e3c2a8d7b6e5f4a3c2b1d0e9f8a7b6c5d4e3f2a1b0c9d8e
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/WebhookEvent' }
responses:
'200': { description: Acknowledged. }
components:
securitySchemes:
apiKey:
type: http
scheme: bearer
description: |
`Authorization: Bearer <api key>`. Sandbox keys start with `sk_test_`, live keys with `sk_live_`.
Keys are issued per client; keep them server-side.
parameters:
IdempotencyKey:
name: Idempotency-Key
in: header
required: true
description: |
A unique value per screening you intend to create (a UUID, or your booking id plus a revision).
Retrying with the same key within 24 hours returns the original screening instead of creating
and billing a second one. Reusing a key with a different body returns `409`.
schema: { type: string, maxLength: 255 }
example: 7d0f4a52-2f7e-4c1e-9a43-6b3c8f1d2e90
schemas:
CheckType:
type: string
description: |
* `denied_party` — every party against international sanctions lists, with politically exposed persons and enforcement lists where enabled for your account.
* `trade_regulation` — every goods line (HS code) against dual-use, military and sanctioned-goods
control lists of the export jurisdiction, with sanctions targeted at the destination country.
* `kyc_ai` — AI-assisted verification of shipper and consignee against public sources: does the
company exist and match the declared name and address, is it active, which sources confirm it.
* `company_report` — (optional add-on) registered company data: status, legal form,
incorporation date, employees, credit rating and recommended credit limit, shareholders.
enum: [denied_party, trade_regulation, kyc_ai, company_report]
ScreeningRequest:
type: object
required: [reference, input]
properties:
reference:
type: string
maxLength: 120
description: Your own reference (booking id, quote id). Returned unchanged and searchable.
checks:
type: array
items: { $ref: '#/components/schemas/CheckType' }
uniqueItems: true
description: Checks to run. Omit to run your account's default checks.
retry_of:
type: string
description: Optional id of an earlier screening this one repeats (for example after a check failed). Only links the two; it does not change billing.
metadata:
type: object
additionalProperties: { type: string, maxLength: 500 }
maxProperties: 20
description: Free key/value pairs stored with the screening and returned unchanged.
input:
oneOf:
- $ref: '#/components/schemas/CargoImpInput'
- $ref: '#/components/schemas/JsonInput'
discriminator:
propertyName: format
mapping:
cargo_imp: '#/components/schemas/CargoImpInput'
json: '#/components/schemas/JsonInput'
CargoImpInput:
type: object
required: [format, fwb]
description: |
Raw IATA Cargo-IMP messages. Supported: FWB (versions 16 and 17) and FHL (version 4 and 5).
**House before master:** when one or more FHL messages are supplied, the parties and goods of each
house are screened; the FWB then supplies routing and flights. Without FHL the FWB parties and
goods are screened. HS codes are read from RTD `/NH/` lines and OCI entries (FWB) and `HTS/` lines (FHL).
An FHL house without `HTS/` codes takes its HS codes from its OCI entries.
An FHL may be a consolidation list: one message with several HBS houses is accepted, and each
house is screened. The FWB agent has no country line; it gets the country of the origin
airport when that is known.
properties:
format: { type: string, const: cargo_imp }
fwb: { type: string, description: 'The FWB message text, line breaks preserved.' }
fhl:
type: array
items: { type: string }
description: Zero or more FHL message texts, one per house air waybill.
JsonInput:
type: object
required: [format, shipment]
properties:
format: { type: string, const: json }
shipment: { $ref: '#/components/schemas/ShipmentInput' }
ShipmentInput:
type: object
required: [parties]
properties:
awb: { type: string, description: 'Master AWB, e.g. 235-12345675.' }
hawb: { type: string }
origin: { $ref: '#/components/schemas/Place' }
destination: { $ref: '#/components/schemas/Place' }
export_country:
type: string
description: ISO country whose export rules apply. Defaults to the origin country.
flights:
type: array
items:
type: object
properties:
flight: { type: string, example: TK1958 }
date: { type: string, format: date }
parties:
type: array
minItems: 1
items: { $ref: '#/components/schemas/PartyInput' }
goods:
type: array
items: { $ref: '#/components/schemas/GoodsInput' }
description: Required for `trade_regulation`.
Place:
type: object
properties:
airport: { type: string, description: IATA airport code. }
country: { type: string, description: ISO 3166-1 alpha-2. }
PartyRole:
type: string
enum: [shipper, consignee, agent, notify, end_user, other]
PartyInput:
type: object
required: [role, name]
properties:
role: { $ref: '#/components/schemas/PartyRole' }
name: { type: string }
address: { $ref: '#/components/schemas/Address' }
registration_number: { type: string, description: 'Chamber of Commerce / company register number, if known. Used to pick the right company for kyc_ai and company_report.' }
vat_number: { type: string }
type:
type: string
enum: [organisation, individual]
default: organisation
Address:
type: object
properties:
street: { type: string }
city: { type: string }
postal_code: { type: string }
region: { type: string }
country: { type: string, description: ISO 3166-1 alpha-2. }
GoodsInput:
type: object
required: [description]
properties:
description: { type: string }
hs_code:
type: string
description: 6 to 14 digits; dots and spaces are ignored. At least 6 digits are needed for trade-regulation screening.
pieces: { type: integer }
gross_weight_kg: { type: number }
cas_number: { type: string, description: 'CAS registry number for chemical substances, e.g. 7789-29-9.' }
ScreeningStatus:
type: string
enum: [processing, completed]
description: '`completed` once every requested check is final (completed, failed or skipped).'
Outcome:
type: string
description: |
* `clear` — nothing found that needs attention.
* `review` — at least one possible match or controlled item: a person should look before the shipment proceeds.
* `incomplete` — at least one check, or part of one, could not be completed (see `error`), a requested check was `skipped`, or part of the shipment was not screened (for example a house without shipper or consignee in a consolidation, see the `fhl_without_parties` warning); nothing was found in what did complete. To try again, create a new screening with a new `Idempotency-Key` (optionally `retry_of`); failed parts were not billed.
enum: [clear, review, incomplete]
CheckStatus:
type: string
enum: [queued, processing, completed, failed, skipped]
description: |
* `completed` — every part of the check finished, or some parts finished and the others carry an `error` in their result.
* `failed` — no part of the check could be completed.
* `skipped` — the input did not contain what the check needs (for example no HS code). The check has no outcome of its own, but the screening's outcome is then at least `incomplete`.
Failed and skipped checks are not billed.
Screening:
type: object
required: [id, object, reference, environment, status, created_at, checks]
properties:
id: { type: string, example: scr_01JC4T8Z6Q2W9V3M7K5N0P1R2S }
object: { type: string, const: screening }
reference: { type: string }
environment: { type: string, enum: [sandbox, live] }
status: { $ref: '#/components/schemas/ScreeningStatus' }
outcome:
oneOf: [{ $ref: '#/components/schemas/Outcome' }, { type: 'null' }]
description: Overall outcome, the most severe of all checks. `null` while processing.
created_at: { type: string, format: date-time }
completed_at: { type: [string, 'null'], format: date-time }
metadata: { type: object, additionalProperties: { type: string } }
shipment: { $ref: '#/components/schemas/ParsedShipment' }
warnings:
type: array
items: { $ref: '#/components/schemas/Warning' }
checks:
type: object
description: One entry per requested check.
properties:
denied_party: { $ref: '#/components/schemas/DeniedPartyCheck' }
trade_regulation: { $ref: '#/components/schemas/TradeRegulationCheck' }
kyc_ai: { $ref: '#/components/schemas/KycAiCheck' }
company_report: { $ref: '#/components/schemas/CompanyReportCheck' }
usage:
type: object
description: What this screening counts towards your invoice. Final once `status` is `completed`.
properties:
billable_checks: { type: integer }
items:
type: array
items:
type: object
properties:
check: { $ref: '#/components/schemas/CheckType' }
units: { type: integer, description: 'Billing units as agreed in your contract. Units follow the screening requests actually made: denied_party one per party screened; trade_regulation one per goods line per control regime screened; kyc_ai and company_report one per party verified. One waybill can therefore count several units. Failed parts are not counted.' }
ParsedShipment:
type: object
description: The shipment as the platform read it. Use it to confirm the message was interpreted as intended.
properties:
source: { type: string, enum: [fwb, fhl, json], description: Where the screened parties and goods came from. }
awb: { type: [string, 'null'] }
houses:
type: array
description: The house waybills read from the FHL messages. Empty when only an FWB was sent; parties and goods then belong to the master.
items:
type: object
properties:
id: { type: string, example: hse_1 }
hawb: { type: string }
origin: { $ref: '#/components/schemas/Place' }
destination: { $ref: '#/components/schemas/Place' }
export_country: { type: string }
flights:
type: array
items: { type: object, properties: { flight: { type: string }, date: { type: string, format: date } } }
parties:
type: array
items:
allOf:
- type: object
required: [id]
properties:
id: { type: string, example: pty_1 }
house_id: { type: [string, 'null'], description: The house this party belongs to; null for the master. }
- $ref: '#/components/schemas/PartyInput'
goods:
type: array
items:
allOf:
- type: object
required: [id]
properties:
id: { type: string, example: gds_1 }
house_id: { type: [string, 'null'], description: The house this goods line belongs to; null for the master. }
- $ref: '#/components/schemas/GoodsInput'
Warning:
type: object
required: [code, message]
properties:
code:
type: string
description: Machine-readable. New codes may be added.
examples:
- hs_code_missing
- hs_code_too_short
- oci_hs_code_unassigned
- goods_description_missing
- party_address_incomplete
- party_country_missing
- party_without_name
- country_inferred_from_party
- fhl_without_parties
- fhl_duplicate_house
- unknown_segment_ignored
message: { type: string }
path: { type: string, description: 'Where in the parsed shipment, e.g. goods[1] or parties[2].address.' }
CheckBase:
type: object
required: [status]
properties:
status: { $ref: '#/components/schemas/CheckStatus' }
outcome:
oneOf: [{ $ref: '#/components/schemas/Outcome' }, { type: 'null' }]
completed_at: { type: [string, 'null'], format: date-time }
error:
oneOf: [{ $ref: '#/components/schemas/CheckError' }, { type: 'null' }]
CheckError:
type: object
properties:
code:
type: string
description: |
Machine-readable. New codes may be added. `input_insufficient`: the input lacks what this
part needs (for example a goods line without an HS code), or the part is not screened, as
for individuals in denied-party screening. `results_partial`: matches are returned, but not
every possible match could be retrieved in full.
examples: [source_unavailable, source_timeout, input_insufficient, results_partial]
message: { type: string }
DeniedPartyCheck:
allOf:
- $ref: '#/components/schemas/CheckBase'
- type: object
properties:
results:
type: array
items:
type: object
properties:
party_id: { type: string }
outcome: { $ref: '#/components/schemas/Outcome' }
error:
oneOf: [{ $ref: '#/components/schemas/CheckError' }, { type: 'null' }]
description: Set when this part could not be completed; the rest of the check still stands.
matches:
type: array
description: Possible matches at or above the screening threshold. Matches below the threshold are not returned. Empty when nothing was found.
items:
type: object
properties:
matched_name: { type: string }
aliases: { type: array, items: { type: string }, description: Other names the listed entity is known by. }
score: { type: integer, minimum: 0, maximum: 100, description: Match strength. }
entity_type: { type: string, enum: [organisation, individual, unknown] }
countries: { type: array, items: { type: string }, description: ISO 3166-1 alpha-2 codes associated with the listed entity. }
categories:
type: array
description: Why this entity is listed. One match can carry several categories.
items: { type: string, enum: [sanctions, former_sanctions, pep, enforcement, adverse_media, state_owned, insolvency, other] }
sanctions:
type: array
description: One entry per sanction listing. Empty for matches that are not sanctions related.
items:
type: object
properties:
authority: { type: [string, 'null'], example: European Union }
programme: { type: [string, 'null'], example: Russia }
measures: { type: array, items: { type: string }, example: [Asset freeze] }
status: { type: string, enum: [current, former] }
sources:
type: array
description: Public sources for the listing. Can be empty.
items:
type: object
properties:
title: { type: [string, 'null'] }
url: { type: string }
TradeRegulationCheck:
allOf:
- $ref: '#/components/schemas/CheckBase'
- type: object
properties:
jurisdiction: { type: string, description: 'Export jurisdiction whose rules were applied, e.g. EU.' }
sanctions_target: { type: string, description: Destination country used for sanctioned-goods screening. }
results:
type: array
items:
type: object
properties:
goods_id: { type: string }
hs_code: { type: string }
outcome: { $ref: '#/components/schemas/Outcome' }
error:
oneOf: [{ $ref: '#/components/schemas/CheckError' }, { type: 'null' }]
description: Set when this part could not be completed; the rest of the check still stands.
regimes:
type: array
description: Each control regime screened for this goods line.
items:
type: object
properties:
regime: { type: string, enum: [dual_use, military, sanctions] }
result:
type: string
enum: [no_match, match, not_applicable, not_covered]
description: |
* `not_applicable` — no such regime exists for this jurisdiction or destination (for example no sanctions programme targets the destination).
* `not_covered` — this regime could not be screened for this jurisdiction. The goods line's outcome is then `incomplete`.
controlled_items:
type: array
items: { $ref: '#/components/schemas/ControlledItem' }
ControlledItem:
type: object
properties:
regime: { type: string, enum: [dual_use, military, sanctions] }
control_number: { type: string, description: Export control classification number or sanctioned item number, example: 1C216 }
hs_code: { type: string, description: The customs code (prefix) the item is correlated with. Can be shorter than the declared HS code. }
cas_number: { type: [string, 'null'] }
description: { type: [string, 'null'] }
category: { type: [string, 'null'] }
control_list:
type: [object, 'null']
properties:
name: { type: string }
reference: { type: [string, 'null'] }
publisher: { type: [string, 'null'] }
url: { type: [string, 'null'] }
note: { type: string, description: 'The match is a correlation via the HS code: it means the goods MAY fall under this entry. Whether they do depends on their technical specifications.' }
KycAiCheck:
allOf:
- $ref: '#/components/schemas/CheckBase'
- type: object
properties:
results:
type: array
items:
type: object
properties:
party_id: { type: string }
outcome: { $ref: '#/components/schemas/Outcome' }
error:
oneOf: [{ $ref: '#/components/schemas/CheckError' }, { type: 'null' }]
description: Set when this part could not be completed; the rest of the check still stands.
confidence: { type: integer, minimum: 0, maximum: 100, description: Overall confidence that the party is who it claims to be. }
summary: { type: string }
findings:
type: array
items:
type: object
properties:
field: { type: string, enum: [name, street, city, postal_code, country, registration_number, activity_status, website] }
declared: { type: [string, 'null'] }
found: { type: [string, 'null'] }
verdict: { type: string, enum: [confirmed, differs, not_found] }
confidence: { type: integer, minimum: 0, maximum: 100 }
explanation: { type: string }
sources: { type: array, items: { type: string, format: uri } }
CompanyReportCheck:
allOf:
- $ref: '#/components/schemas/CheckBase'
- type: object
properties:
results:
type: array
items:
type: object
properties:
party_id: { type: string }
outcome: { $ref: '#/components/schemas/Outcome' }
error:
oneOf: [{ $ref: '#/components/schemas/CheckError' }, { type: 'null' }]
description: Set when this part could not be completed; the rest of the check still stands.
company:
type: object
properties:
registered_name: { type: [string, 'null'] }
registration_number: { type: [string, 'null'] }
status: { type: string, enum: [active, inactive, pending, unknown] }
status_detail: { type: [string, 'null'], description: 'The local status wording, e.g. "In liquidation" or "Dissolved".' }
legal_form: { type: [string, 'null'] }
incorporated_on: { type: [string, 'null'], format: date }
employees: { type: [integer, 'null'], description: Most recently reported number of employees. }
credit_rating: { type: [string, 'null'], enum: [A, B, C, D, E, null], description: 'A (lowest risk) to E (highest risk); null when not rated.' }
probability_of_default: { type: [number, 'null'], description: 'Estimated probability of default within 12 months, in percent.' }
recommended_credit_limit: { type: [object, 'null'], properties: { amount: { type: number }, currency: { type: string } } }
shareholders:
type: array
description: Current shareholders. Can be empty, depending on the country of registration.
items:
type: object
properties:
name: { type: string }
type: { type: string, enum: [person, company, other] }
share_percentage: { type: [number, 'null'] }
Account:
type: object
properties:
object: { type: string, const: account }
id: { type: string }
name: { type: string }
environment: { type: string, enum: [sandbox, live] }
enabled_checks: { type: array, items: { $ref: '#/components/schemas/CheckType' } }
default_checks: { type: array, items: { $ref: '#/components/schemas/CheckType' } }
input_formats: { type: array, items: { type: string, enum: [cargo_imp, json] } }
webhook_url: { type: [string, 'null'] }
WebhookEvent:
type: object
required: [id, type, created_at, data]
properties:
id: { type: string, example: evt_01JC4TB2M8X7Y6Z5A4B3C2D1E0 }
type: { type: string, const: screening.completed }
created_at: { type: string, format: date-time }
data: { $ref: '#/components/schemas/Screening' }
Error:
type: object
required: [error]
properties:
error:
type: object
required: [code, message]
properties:
code: { type: string }
message: { type: string }
param: { type: [string, 'null'], description: The request field the error refers to. }
request_id: { type: string, description: Quote this when contacting support. }
responses:
InvalidRequest:
description: The request is malformed (missing field, wrong type, unknown check).
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example: { error: { code: invalid_request, message: "'reference' is required.", param: reference, request_id: req_8f3a2c } }
Unauthenticated:
description: Missing or unknown API key.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example: { error: { code: unauthenticated, message: Invalid API key., param: null, request_id: req_8f3a2d } }
Forbidden:
description: The key is valid but not allowed to do this (account inactive, check not enabled, sandbox key on production).
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example: { error: { code: check_not_enabled, message: "'company_report' is not enabled for your account.", param: checks, request_id: req_8f3a2e } }
NotFound:
description: No screening with this id for your account.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
IdempotencyConflict:
description: The Idempotency-Key was already used with a different request body.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example: { error: { code: idempotency_conflict, message: This Idempotency-Key was used with a different request., param: Idempotency-Key, request_id: req_8f3a2f } }
UnprocessableMessage:
description: The message could be read as JSON but contains nothing screenable (for example an FWB without any party). Not billed.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example: { error: { code: no_screenable_content, message: 'No shipper, consignee or agent found in the FWB message.', param: input.fwb, request_id: req_8f3a30 } }
RateLimited:
description: Too many requests. Retry after the number of seconds in the `Retry-After` header.
headers:
Retry-After: { schema: { type: integer } }
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
ServerError:
description: Something went wrong on our side. Safe to retry with the same Idempotency-Key.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
examples:
ScreeningProcessing:
summary: Accepted, fast checks done, AI verification still running
value:
id: scr_01JC4T8Z6Q2W9V3M7K5N0P1R2S
object: screening
reference: CAI-BKG-2026-118842
environment: sandbox
status: processing
outcome: null
created_at: '2026-11-03T09:14:02Z'
completed_at: null
checks:
denied_party: { status: completed, outcome: clear, completed_at: '2026-11-03T09:14:04Z', error: null }
trade_regulation: { status: completed, outcome: review, completed_at: '2026-11-03T09:14:05Z', error: null }
kyc_ai: { status: processing, outcome: null, completed_at: null, error: null }
ScreeningCompleted:
summary: Completed screening of the example FWB
value:
id: scr_01JC4T8Z6Q2W9V3M7K5N0P1R2S
object: screening
reference: CAI-BKG-2026-118842
environment: sandbox
status: completed
outcome: review
created_at: '2026-11-03T09:14:02Z'
completed_at: '2026-11-03T09:14:51Z'
metadata: {}
shipment:
source: fwb
awb: 235-12345675
houses: []
origin: { airport: AMS, country: NL }
destination: { airport: IST, country: TR }
export_country: NL
flights: [{ flight: TK1958, date: '2026-11-14' }]
parties:
- id: pty_1
house_id: null
role: shipper
name: TULIPA PRECISION METALS BV
type: organisation
address: { street: INDUSTRIEWEG 12, city: EINDHOVEN, postal_code: 5651GK, country: NL }
- id: pty_2
house_id: null
role: consignee
name: BOSPORUS TEKNIK MAKINA AS
type: organisation
address: { street: ORGANIZE SANAYI BOLGESI 4 CAD NO 7, city: ISTANBUL, postal_code: '34906', country: TR }
- id: pty_3
house_id: null
role: agent
name: NORTHDYKE FORWARDING BV
type: organisation
address: { city: AMSTERDAM, country: NL }
goods:
- { id: gds_1, house_id: null, description: MARAGING STEEL BARS, hs_code: '72281050', pieces: 2, gross_weight_kg: 480.0 }
- { id: gds_2, house_id: null, description: STEEL RETAINING RINGS, hs_code: '73182900', pieces: 1, gross_weight_kg: 132.0 }
warnings:
- code: party_address_incomplete
message: Agent has no street address; screened on name, city and country.
path: parties[2].address
checks:
denied_party:
status: completed
outcome: clear
completed_at: '2026-11-03T09:14:04Z'
error: null
results:
- { party_id: pty_1, outcome: clear, matches: [] }
- { party_id: pty_2, outcome: clear, matches: [] }
- { party_id: pty_3, outcome: clear, matches: [] }
trade_regulation:
status: completed
outcome: review
completed_at: '2026-11-03T09:14:05Z'
error: null
jurisdiction: EU
sanctions_target: TR
results:
- goods_id: gds_1
hs_code: '72281050'
outcome: review
regimes:
- { regime: dual_use, result: match }
- { regime: military, result: no_match }
- { regime: sanctions, result: not_applicable }
controlled_items:
- regime: dual_use
control_number: 1C216
hs_code: '72281050'
category: 1 - Special materials and related equipment / C - Materials
description: "Maraging steel, other than that specified in 1C116, 'capable of' an ultimate tensile strength of 1 950 MPa or more, at 293 K (20 °C). Note: 1C216 does not control forms in which all linear dimensions are 75 mm or less. Technical note: the phrase maraging steel 'capable of' encompasses maraging steel before or after heat treatment."
control_list:
name: Regulation (EU) 2021/821 — Union regime for the control of exports, brokering, technical assistance, transit and transfer of dual-use items
reference: 02021R0821-20251115
publisher: European Union
url: https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:02021R0821-20251115
note: The match is a correlation via the HS code. The goods fall under 1C216 only if they meet its technical specification; an export licence is then required.
- goods_id: gds_2
hs_code: '73182900'
outcome: clear
regimes:
- { regime: dual_use, result: no_match }
- { regime: military, result: no_match }
- { regime: sanctions, result: not_applicable }
controlled_items: []
kyc_ai:
status: completed
outcome: review
completed_at: '2026-11-03T09:14:51Z'
error: null
results:
- party_id: pty_1
outcome: clear
confidence: 96
summary: Registered Dutch private company at the declared address, active, manufacturer of precision metal parts.
findings:
- { field: name, declared: TULIPA PRECISION METALS BV, found: Tulipa Precision Metals B.V., verdict: confirmed, confidence: 98, explanation: Company register lists the same legal name; capitalisation and punctuation differ only., sources: ['https://register.example/nl/tulipa-precision-metals'] }
- { field: street, declared: INDUSTRIEWEG 12, found: Industrieweg 12, verdict: confirmed, confidence: 95, explanation: Registered office address matches., sources: ['https://register.example/nl/tulipa-precision-metals'] }
- { field: activity_status, declared: null, found: active, verdict: confirmed, confidence: 94, explanation: Registration is active; no dissolution or bankruptcy notice found., sources: ['https://register.example/nl/tulipa-precision-metals'] }
- party_id: pty_2
outcome: review
confidence: 71
summary: Company found in Istanbul with a matching name, but the registered postal code differs from the declared one.
findings:
- { field: name, declared: BOSPORUS TEKNIK MAKINA AS, found: Bosporus Teknik Makina A.Ş., verdict: confirmed, confidence: 93, explanation: Trade registry gazette lists the company under this name., sources: ['https://gazette.example/tr/bosporus-teknik'] }
- { field: postal_code, declared: '34906', found: '34953', verdict: differs, confidence: 80, explanation: The trade registry and the company website both give 34953 (Tuzla organised industrial zone)., sources: ['https://gazette.example/tr/bosporus-teknik', 'https://bosporus-teknik.example/contact'] }
usage:
billable_checks: 3
items:
- { check: denied_party, units: 3 }
- { check: trade_regulation, units: 4 }
- { check: kyc_ai, units: 2 }
Still provisional
- The product name. The hostnames are final:
sandbox.qcargoapi.comfor the sandbox andqcargoapi.comfor production. Your sandbox key follows with onboarding. - Field names may still change slightly before the first live release. Changes after that will be additive only.
- We welcome your feedback on the contract, especially on how your booking flow wants to receive results.