Nexiel AgentOS is four narrow, supervised agents. An Onboarding Agent runs a registry, UBO-plausibility, and screening check on a new client's signup input and drafts a recommendation from it. A KYB Review Copilotsummarizes an application's evidence into a draft recommendation for a human reviewer. An EUDI Onboarding Copilotdrafts a client's readiness checklist for registering as an EUDI relying party in a given jurisdiction. A Legislation Watch Copilot proposes changes to which country and restricted-purchase-category pairs support in-store age verification. The first two are HTTP endpoints on @nexiel/screen-api. The latter two have no public API route and are called only from the reviewer dashboard.
Read this section first. All four agents draft, summarize, propose, or recommend. None of them can apply the decision it is advising on. Each has its own single, narrow write target, enforced by an automated test, not just documentation:
This mirrors the same rule Nexiel Screen already applies to sanctions/PEP matching: a POSSIBLE_MATCH is never auto-cleared, regardless of how confident the underlying model is (see the Screen API docs). The EU AI Act requires human-in-the-loop review for AI-assisted decisions like these. That requirement is enforced structurally here, not left to a policy document.
Given a prospective client's free-text signup input, the Onboarding Agent normalizes it and runs the same registry lookup, UBO/director plausibility check, and entity/person sanctions and PEP screening a real application would go through, but it never creates a client or KYB application. It only ever drafts a recommendation, SUBMIT_RECOMMENDED, NEEDS_INFO_RECOMMENDED, or DO_NOT_SUBMIT_RECOMMENDED, plus a plain-English rationale, deliberately spelled differently from an application's own status values so a draft can never be mistaken for a decision.
This endpoint is reachable with no API key at all, in every service mode: it is the first touch a prospective client makes, before any account exists for them to authenticate as. Because there is no client identity to rate-limit by yet, it is guarded by a source-IP-keyed rate limit rather than the per-client limiter every other route in this API uses, sized for a legitimate applicant submitting once.
The examples below are real, executed against https://compliance.nexiel.io/v1/onboarding/kyb-applications, still no API key required:
curl -s -X POST https://compliance.nexiel.io/v1/onboarding/kyb-applications \
-H "Content-Type: application/json" \
-H "Idempotency-Key: docs-example-key-1" \
-d '{
"legalName": "Nordlys Consulting AS",
"registryCountry": "NO",
"registryNumber": "923609016",
"persons": [
{ "role": "Beneficial Owner", "fullName": "Kari Nordmann", "dob": "1978-03-14", "nationality": "NO", "ownershipPercentage": 100 }
]
}'{
"draftId": "c4c31f22-4e13-4e54-a2e8-d6cab6ae7602",
"recommendation": "SUBMIT_RECOMMENDED",
"registryResult": "VERIFIED",
"plausibilityPassed": true,
"screeningHit": false,
"advisoryFlags": [
"Person 1 (Kari Nordmann): role \"Beneficial Owner\" normalized to \"UBO\" - confirm this is correct."
],
"idempotencyReplay": false
}registryNumber above is a real Norwegian organization number, checked against the live Brønnøysund registry. advisoryFlags is informational context for a human or downstream system, never a gate. recommendation is SUBMIT_RECOMMENDED here because the registry, plausibility, and screening checks all came back clean, but that is a recommendation, not an approval: draftId is a draft row id, not a KYB application id, and no application exists yet.
curl -s -X POST https://compliance.nexiel.io/v1/onboarding/kyb-applications \
-H "Content-Type: application/json" \
-d '{
"legalName": "Skyggeselskap Ukjent AS",
"registryCountry": "NO",
"registryNumber": "000000001",
"persons": [
{ "role": "Director", "fullName": "Ola Testperson" }
]
}'{
"draftId": "1a08c312-33a0-40bd-9dac-17d6dd7dbfe4",
"recommendation": "NEEDS_INFO_RECOMMENDED",
"registryResult": "NOT_FOUND",
"plausibilityPassed": false,
"screeningHit": false,
"advisoryFlags": [],
"idempotencyReplay": false
}A registry number the real Brønnøysund register has no record of, and no declared UBO or senior managing official (AMLD requires at least one), together drive NEEDS_INFO_RECOMMENDED, a recoverable gap, never the most severe recommendation.
A screening hit on the entity or any declared person is this draft's single most severe signal:
{
"draftId": "2b4b6a9e-9f2a-4e12-9b7d-7e2b8f6a1c3d",
"recommendation": "DO_NOT_SUBMIT_RECOMMENDED",
"registryResult": "VERIFIED",
"plausibilityPassed": true,
"screeningHit": true,
"advisoryFlags": [],
"idempotencyReplay": false
}DO_NOT_SUBMIT_RECOMMENDEDis a recommendation, not a block. A human reviewer can still open a real KYB application from this draft and look at the candidate themselves. Only a human's own decision can ever reject an application.
Idempotency-Key is enforced server-side, scoped by registry country and number rather than a client id, since no account necessarily exists yet at this point. Resending the exact same submission with the same key returns the original result verbatim:
{
"draftId": "c4c31f22-4e13-4e54-a2e8-d6cab6ae7602",
"recommendation": "SUBMIT_RECOMMENDED",
"registryResult": "VERIFIED",
"plausibilityPassed": true,
"screeningHit": false,
"advisoryFlags": [
"Person 1 (Kari Nordmann): role \"Beneficial Owner\" normalized to \"UBO\" - confirm this is correct."
],
"idempotencyReplay": true
}Reusing the same key against a materially different submission is a 409, not a silent overwrite:
{
"error": "idempotency_key_conflict",
"message": "Idempotency-Key was already used for a different onboarding submission (different legalName/registryCountry/registryNumber/persons). Use a new key for a genuinely new application, or resend the exact original request to get back its original result."
}And a malformed request never reaches the agent at all:
curl -s -X POST https://compliance.nexiel.io/v1/onboarding/kyb-applications \
-H "Content-Type: application/json" \
-d '{"legalName": "Missing Fields Co"}'{
"error": "bad_request",
"message": "body must have required property 'registryCountry'"
}A draft is inert until a human reviewer explicitly acts on it. In the reviewer dashboard, every drafted recommendation is listed. A reviewer who decides a draft is worth pursuing submits it as a real KYB application, which always lands in the review queue unconditionally, regardless of how clean the evidence looked or what the draft's own recommendation said. From there it can only reach a final decision through a second, fully independent human review, the same queue every other application goes through.
Registry verification, UBO plausibility, and sanctions/PEP screening all run again, for real, at the moment of conversion, rather than trusting the draft's evidence, which may be stale, since sanctions and PEP lists refresh daily. A draft can be converted into a real application at most once, enforced at the database level, not just a disabled button in the UI.
Once a draft becomes a real application, it is reviewed through the exact same queue as any other application, including by the KYB Review Copilot below. The drafting stage stays a separate table with its own vocabulary because it answers a different question: “should a human even open a case for this signup?” versus “should this already-open case be approved or rejected?”
The Onboarding Agent's free-text normalization runs through one of two implementations: a real, Anthropic-backed drafter that catches things pattern-matching cannot, like a company name and registry number that look internally inconsistent, or a deterministic, rule-based drafter that recognizes common free-text role synonyms (“Managing Director” → DIRECTOR, “Beneficial Owner” → UBO) and basic field hygiene, without attempting semantic judgement. Sandbox mode always uses the deterministic drafter. Production requires the real one to be configured, with no silent fallback between the two.
For an application awaiting review, the Review Copilot reads its evidence, the registry check, UBO plausibility flags, and entity/person screening results, and summarizes it into a plain-English draft recommendation for a human reviewer: APPROVE_RECOMMENDED, REJECT_RECOMMENDED, or ESCALATE_RECOMMENDED, each with a rationale citing the specific evidence behind it. These are spelled deliberately differently from an application's own status vocabulary, so a draft can never be mistaken for, or mechanically coerced into, an actual decision.
This endpoint is gated behind a distinct reviewer role, never a customer's own API key, so a customer token can never fetch a draft recommendation, including other applicants' UBO names in its rationale, for an arbitrary application. In production it is served at the same path, on https://compliance.nexiel.io/v1/kyb-applications/{id}/review-drafts.
curl -s -X POST https://compliance.nexiel.io/v1/kyb-applications/1a08c312-33a0-40bd-9dac-17d6dd7dbfe4/review-drafts \
-H "Authorization: Bearer <reviewer_access_token>"{
"id": "ad2a9ea6-a802-437a-9300-7af2bb496c96",
"applicationId": "1a08c312-33a0-40bd-9dac-17d6dd7dbfe4",
"recommendation": "REJECT_RECOMMENDED",
"rationale": [
{
"code": "RECOMMENDATION_DRIVER_REGISTRY_NOT_ACTIVE",
"detail": "Registry verification returned NOT_FOUND - the entity does not appear to be a going concern."
},
{
"code": "REGISTRY_NOT_FOUND",
"detail": "Registry verification (NO 000000001) returned NOT_FOUND."
},
{
"code": "PLAUSIBILITY_NO_UBO_OR_SENIOR_MANAGING_OFFICIAL",
"detail": "no natural-person UBO or senior-managing-official-of-last-resort was declared (AMLD requires at least one)"
},
{
"code": "PLAUSIBILITY_MISSING_DOB",
"detail": "Ola Testperson has no date of birth on file"
},
{
"code": "PLAUSIBILITY_MISSING_NATIONALITY",
"detail": "Ola Testperson has no nationality on file"
},
{
"code": "ENTITY_SCREENING_CLEAR",
"detail": "Entity screening returned CLEAR (0 candidate(s) above the reporting threshold)."
},
{
"code": "UBO_SCREENING_CLEAR",
"detail": "Screening for Ola Testperson returned CLEAR (0 candidate(s) above the reporting threshold)."
}
],
"evidenceSummary": {
"applicationId": "1a08c312-33a0-40bd-9dac-17d6dd7dbfe4",
"registryCheck": {
"result": "NOT_FOUND",
"checkId": "21489322-5033-4c22-a0e0-1a4178623ed5"
},
"uboScreenings": [
{
"uboId": "79759469-d54f-45e7-8970-c0344b047452",
"checkId": "cc9679bb-d8b6-4643-a183-66c94b5136eb",
"candidateCount": 0,
"classification": "CLEAR"
}
],
"entityScreening": {
"checkId": "bba30d1d-1a47-474e-9580-1a768c82d4b9",
"candidateCount": 0,
"classification": "CLEAR"
},
"uboPlausibility": {
"result": "FLAGGED",
"checkId": "291c3e05-3400-48f1-b8de-922a5d015109",
"flagCount": 3
}
},
"createdAt": "2026-07-05T14:42:34.938Z"
}This recommendation was driven by the registry result and plausibility gaps, not a sanctions hit, both screening results above are CLEAR. Had either come back CONFIRMED_MATCH, that alone would drive REJECT_RECOMMENDED regardless of everything else. POSSIBLE_MATCH anywhere drives ESCALATE_RECOMMENDED, deferring to a reviewer's own judgement rather than second-guessing the matching engine. The copilot only recommends APPROVE_RECOMMENDED for a genuinely clean application whose only reason for needing review is a minor gap, like a missing date of birth or nationality. Every other case escalates or rejects. It never defaults to approval when it is not sure.
Every call persists a new row rather than overwriting a single “current draft”, so a regulator or internal auditor can see exactly what the copilot said and when, across an application's whole review lifetime. Fetch the latest one without generating a new one:
curl -s https://compliance.nexiel.io/v1/kyb-applications/1a08c312-33a0-40bd-9dac-17d6dd7dbfe4/review-drafts/latest \
-H "Authorization: Bearer <reviewer_access_token>"404 for an application id that does not exist, and 409, never a silent no-op, for one that is not currently awaiting review:
{
"error": "application_not_found",
"message": "no KYB application found for id \"00000000-0000-0000-0000-000000000000\""
}{
"error": "application_not_in_review",
"message": "application \"c4c31f22-4e13-4e54-a2e8-d6cab6ae7602\" is APPROVED, not NEEDS_REVIEW - the review copilot only drafts recommendations for applications currently awaiting human review."
}Refusing to draft anything for an already-decided application is deliberate: a fresh recommendation against a case a human already ruled on would read as second-guessing that decision.
Every bank, retailer, employer, or marketplace using Nexiel must register as an EUDI relying party itself, separately, per EU member state, through that jurisdiction's own Registration Authority. Given a client id and a jurisdiction, this copilot reads the client's already-on-file data and that jurisdiction's reference requirements, and summarizes them into a checklist plus one of three recommendations: READY_TO_SUBMIT_RECOMMENDED, GAPS_REMAIN_RECOMMENDED, or JURISDICTION_UNSUPPORTED_RECOMMENDED.
Unlike the two agents above, this one has no public API route.It is reachable only from the reviewer dashboard, by an authenticated reviewer. It never submits anything anywhere, no submission mechanism of any kind exists, and it never touches the table holding a client's real registration status, not even a read. Moving a registration to submitted, registered, or rejected always requires an authenticated reviewer to affirmatively state what happened on the jurisdiction's real Registration Authority channel.
In-store and self-checkout age verification for a restricted-purchase category (alcohol, tobacco, lottery) is gated per country and category: every pair defaults to not supported, and there is no fully-autonomous enablement path anywhere in this system. This copilot takes a manually curated, counsel-reviewed list of legislation signals, not a live web-scraper, and for each signal that would change a country/category's current status, proposes exactly one pending-review entry, citing the evidence and a confidence level. It never writes the live status directly, even when its own suggestion is to enable something, and it never touches the denormalized “what's live” table beyond reading it.
Like the EUDI Onboarding Copilot, this one also has no public API route. It runs from a signal-ingestion job, and its only consumer is the reviewer dashboard's legislation queue. Approving, rejecting, or manually overriding a status are the only ways any country/category pair ever goes live, all requiring an authenticated reviewer.
A reviewer dashboard covers screening decisions, KYB applications, EUDI relying-party onboarding, and restricted-category legislation, alongside separate invoice and policy-engine reviewer pages outside AgentOS's scope. The KYB page surfaces the Review Copilot's draft recommendations. The EUDI onboarding page surfaces the EUDI Onboarding Copilot's readiness checklists and lets a reviewer record what happened on the real Registration Authority channel. The legislation page surfaces the Legislation Watch Copilot's pending proposals alongside a manual-override form.
Access is gated by staff login: a real Authorization Code + PKCE flow against the same Keycloak realm Nexiel Screen itself uses, never a separate custom login mechanism. Every page and action under the reviewer dashboard re-checks the reviewer role is still present, rather than trusting a session cookie's mere presence.
The Onboarding Agent's and KYB Review Copilot's endpoints are documented in the same OpenAPI spec as POST /v1/screen (see the Screen API reference). The EUDI Onboarding Copilot and Legislation Watch Copilot have no OpenAPI entry, consistent with having no public API route.