# Trulioo Developer

> **Early access.** Security controls are in place. Tool names, input fields, and
> documentation may change as we incorporate feedback.
>
> One MCP connection gives any agent KYC, KYB, and Know Your Agent (KYA): a signed,
> scoped Digital Agent Profile (DAP) so anyone can prove the organization behind an agent.
> Sandbox uses client-managed OAuth 2.1. Live integrations bootstrap an MCP access token
> with an approved Trulioo `client_id` and `client_secret`.

This is the human docs page rendered as markdown for agents.

## Let your agent guide your KYB setup

Paste this into your coding agent:

```
Help me connect Trulioo MCP and complete a KYB verification in sandbox.
Read https://mcp.trulioo.com/agent-guide.md first. Check whether Trulioo is already
connected, then guide me through business discovery, verification, polling, and report
retrieval. Stay within the KYB tools documented in the guide.
```

[agent-guide.md](/agent-guide.md) is a focused early access guide for connection checks,
OAuth, business search, optional candidate confirmation, verification, polling, and
report retrieval.

## Quickstart: zero to verified

1. **Connect the MCP server** - point any MCP client at the hosted endpoint. It discovers the authorization server and registers itself, so there is no key to copy-paste.
2. **Call a verification tool** - run `kyc_verify` or `kyb_verify` against synthetic test entities; branch on the typed result.
3. **Give your agent an identity** - verify the organization behind the agent, then mint a signed, scoped attestation with `kya_issue_mandate`.
4. **Let anyone verify it** - publish the attestation in your A2A card; any party checks it against the issuer JWKS, no key needed.

### I want to...
| Goal | Do this |
|---|---|
| Verify a person | `kyc_verify` |
| Verify a business | `kyb_verify` |
| Screen a name on its own | `aml_screen` - per-account |
| Capture a document or a selfie | `docv_create_session` - per-account |
| Prove who is behind an agent | Know Your Agent (`kya_issue_mandate`) |
| Verify another agent's profile | `kya_verify_agent` |
| Connect an MCP client | see Client setup |
| Authenticate with OAuth | see Authentication |

## The MCP endpoint

- URL: `https://mcp.trulioo.com/mcp`
- Transport: MCP streamable HTTP
- Sandbox auth: client-managed OAuth 2.1 + PKCE.
- Live auth: exchange an approved Trulioo `client_id` and `client_secret` at
  `POST https://mcp.trulioo.com/oauth/token`, then use the returned bearer token on `/mcp`.

## Authentication

### Sandbox connection

Claude Code, Cursor, Codex, ChatGPT, and other current MCP clients manage OAuth 2.1 + PKCE
for you.

1. Add `https://mcp.trulioo.com/mcp` as a remote MCP server.
2. Complete the browser consent flow when your client opens it.
3. Choose sandbox.
4. Return to the client. It initializes the MCP session and discovers the available tools.

Let the client manage registration, PKCE, tokens, refresh, and MCP session headers. Do not
copy sandbox tokens into the client configuration.

### Sandbox

Sandbox is a session type, not a separate URL - it uses the same endpoint, tools, and schemas.
Choose it on the consent screen.

- Unbilled by construction: sandbox sends `VerificationType: Demo`, decided in one place for
  every verify tool, and the mode is pinned in the token - it is not a request parameter and
  cannot be set per call. An unrecognized mode claim is rejected with `401`; it is never
  downgraded to sandbox nor inferred from anything in the request.
- Results are self-describing: `{"test_mode":{"sandbox":true,"verification_type":"Demo"}}`,
  and the server's `instructions` at `initialize` states the mode in words.
- Some rails answer from an official register even in sandbox (the register source is a
  server-side configuration; measured at `mcp.trulioo.com`, 2026-08-25, the
  payload carried `registerSource: "live"`). Then the block reads `"data":"live"` with a
  notice: the values are real, the call is still unbilled, and it is not a verification you
  may rely on. Branch on `test_mode.data`, not only `test_mode.sandbox`.
- No PII stored.

### Live integration with client credentials

Store the approved account's `client_id` and `client_secret` in a secret manager or
environment. Exchange them for a short-lived MCP access token:

```bash
export TRULIOO_CLIENT_ID="your-client-id"
export TRULIOO_CLIENT_SECRET="your-client-secret"

# curl sends Authorization: Basic base64(client_id:client_secret)
curl -sS -X POST https://mcp.trulioo.com/oauth/token \
  -u "$TRULIOO_CLIENT_ID:$TRULIOO_CLIENT_SECRET" \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials'
```

The response has this shape:

```json
{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600
}
```

Never send `client_secret` to `/mcp`, put it in a URL, or log it. The bootstrap does not
return a refresh token. Re-run the bootstrap when the MCP token expires or `/mcp` returns
`401`.

For raw HTTP, set `TOKEN` to the returned `access_token`, then complete the session handshake:

```bash
export TOKEN="eyJ..."
HEADER_FILE="$(mktemp)"

# 1. Initialize and capture the session id from the response headers.
curl -sN -D "$HEADER_FILE" https://mcp.trulioo.com/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
       "protocolVersion":"2025-06-18","capabilities":{},
       "clientInfo":{"name":"my-agent","version":"1.0.0"}}}'
export SID="$(awk 'tolower($1) == "mcp-session-id:" {gsub(/\r/, "", $2); print $2}' "$HEADER_FILE")"
rm -f "$HEADER_FILE"
test -n "$SID"

# 2. Tell the server the client is ready; expect 202 with no body.
curl -sN -X POST https://mcp.trulioo.com/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -H 'MCP-Protocol-Version: 2025-06-18' \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

# 3. The initialized session can now list or call tools.
curl -sN https://mcp.trulioo.com/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -H 'MCP-Protocol-Version: 2025-06-18' \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
```

### Scopes

`verify` (run verifications, issue KYA credentials) and `read` (fetch records, statuses,
reports). Both granted by default; request `read` alone for an analysis-only integration.
KYA's public reads (transparency tree head, inclusion proof, status list, rails) need no
account and are served anonymously.

### Usage limits

Usage limits apply during early access. If a result includes `retry_after_seconds`, wait for
that interval before retrying. Contact Trulioo before increasing production traffic.

## Client setup

Use the hosted endpoint in your MCP client. The first protected action opens the OAuth
consent flow.

**Claude Code**
```
claude mcp add --transport http trulioo https://mcp.trulioo.com/mcp
```

**Claude Desktop** - add Trulioo MCP as a remote (custom) connector by URL:
`https://mcp.trulioo.com/mcp` (on the plans that support custom connectors).
Complete OAuth on the consent screen.

**MCP config (JSON)** - any client (Cursor, VS Code, ...):
```json
{ "mcpServers": { "trulioo": { "url": "https://mcp.trulioo.com/mcp" } } }
```

**OpenAI Codex**
```
codex mcp add trulioo --url https://mcp.trulioo.com/mcp
```

**ChatGPT (Developer mode)** - add `https://mcp.trulioo.com/mcp` by URL and complete OAuth
on the consent screen.

**Raw JSON-RPC** - discover the tools. Both a bearer token and an initialized session are
required; complete the executable three-step Authentication example first:
```
curl -sN https://mcp.trulioo.com/mcp \
  -H "Authorization: Bearer $TOKEN" -H "mcp-session-id: $SID" \
  -H 'MCP-Protocol-Version: 2025-06-18' \
  -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/list"}'
```

## Tools reference

Every capability is a typed, discoverable MCP tool - there is no raw passthrough, so a tool
that is not on your connection is not reachable by another route. Call `tools/list` for the
exact enabled set: a default progressive connection advertises 17 resident tools and exposes
a default union of 42 tools through runtime discovery. Per-account families change that set.

Call a tool (bearer token and an initialized session both required - see Authentication):
```
curl -sN https://mcp.trulioo.com/mcp \
  -H "Authorization: Bearer $TOKEN" -H "mcp-session-id: $SID" \
  -H 'MCP-Protocol-Version: 2025-06-18' \
  -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"kyc_verify","arguments":{"country_code":"US","data_fields":{}}}}'
```

**Identity & business:** `kyc_verify`, `kyc_get_status`, `kyc_get_record`,
`kyb_verify`, `kyb_search`, `kyb_get_report`, `kyb_registration_lookup`,
`kyb_get_partial_result`, `kyb_disambiguate_candidate`
**Retrieval by transactionId:** `transaction_lookup` (resolve a TransactionID or
TransactionRecordID to the record + identity, KYC or KYB)
**Per-account families** - off unless your account is entitled to them, each enabled on its
own. They are real and supported; they are simply absent from `tools/list` until your
deployment has them. Ask Trulioo for the families you need rather than coding around them, and
read the result from `tools/list` - getting one family does not get you the rest:
`aml_screen` (standalone sanctions/PEP screening),
`docv_create_session`, `docv_get_result`, `docv_cancel_session`, `docv_create_mobile_handoff`
(document and liveness capture - the session flow keeps images off the MCP stream),
age assurance (`age_check` from a known date of birth; `age_estimate_biometric` is declared
but returns `NotImplemented` until biometric estimation ships - do not build against it), and
ongoing monitoring (`monitoring_enroll`, `monitoring_get_alert`, `monitoring_refresh`,
`monitoring_cancel`, `monitoring_get_enrollment`)
**Post-KYB research** - off unless the account is entitled to it:
`kyb_run_follow_up` starts an explicitly approved, bounded ownership or Deep Research follow-up
from a terminal KYB verification; `kyb_get_research` polls Deep Research and `ubo_get_graph`
polls the ownership graph. Direct research and ownership starts are retired.
> `tools/list` and `config_discover_account` are the authority on what YOUR connection has.
> Do not hardcode this list, and do not synthesize a tool name that is not on it.
**Know Your Agent:** `kya_admission_challenge` (mint a short-lived challenge bound
to one privileged target tool), `kya_possession_challenge` (mint the single-use nonce a keyed
card's possession proof must carry - call it first, `kya_issue_mandate` rejects a proof
without a fresh one), `kya_issue_mandate`, `kya_card_fingerprint` (the pre-flight: compute
the fingerprint a build WOULD get, without registering or attesting, so you learn whether a
change re-mints your identity before you spend an issuance on it - free, but keyed),
`kya_verify_agent`, `kya_verify_web_bot_auth`,
`kya_lookup`, `kya_verify_protocol`, `kya_supersede_agent` (rotate/reissue),
`kya_retire_agent` (revoke standing)
The relying-party half - a verifier reads and checks, it does not only verify:
`kya_verify_mandate` (for a credential a counterparty PRESENTED to you - signature, issuer,
window, and with `freshness: central_fresh` revocation; pass `amount` for an ADVISORY read
of the declared ceiling, which never changes `valid`, and where a `within` of `null` means
unanswerable rather than a pass),
`kya_get_mandate` (status, window, and the signed scope; a `max_amount` of `null` means
uncapped, never a cap of zero),
`kya_record_spend` (record a settled spend, which is what makes a `day`/`total` ceiling
answerable at all - your `settlement_id` is the de-duplication key, so a replay with the same
amount returns `recorded: false` and counts once while the same id with a DIFFERENT amount is
refused `409`; recording is not enforcement, it moves no money and blocks nothing),
`kya_mandate_spend` (read the ledger: totals per currency, the declared ceiling, and the
headroom left - a `remaining` of `null` is NOT zero, it means there is no headroom to speak of,
either uncapped or a per-transaction cap, which limits each purchase rather than depleting),
`kya_revoke_mandate` (with a recorded reason),
`kya_revocations`, and four public reads that need no account because Halo serves the
same routes anonymously: `kya_transparency_sth` (the signed tree head every proof hangs
from), `kya_inclusion_proof` (RFC 6962 - verify the audit path with
`verify_inclusion_hashed`, not `verify_inclusion`, because the served `leaf_hash` is
already hashed), `kya_status_list` (the signed revocation bitstring, re-signed per
request so do not cache it by byte hash), `kya_rails` (what each rail demands before you
build for it). Account-backed agent infrastructure checks include
`kya_assess_readiness` (measure public agent-facing documents and return reason codes,
not a score), `kya_assess_artifact` (submit one immutable release or exact Git commit
for a tenant-scoped multi-collector assessment), and `kya_assessment_status` (read its
lifecycle and frozen collector coverage). Assessment status is coverage intent, not a
verdict: partial, failed, canceled, expired, and unavailable never mean clean, and no
vendor score, grade, or rank is exposed. `kya_register_artifact`, `kya_publish_artifact`, and
`kya_revoke_artifact` (register, publish, or permanently revoke an immutable software
artifact release). One more needs an account and its subject is not a credential:
`kya_verify_client` (fetch and validate the Client ID Metadata Document at an OAuth
client's https `client_id` - the registration mechanism MCP `2026-07-28` asks for - and
get back its origin, its `redirect_uris`, whether it publishes a key, any KYA operator
claim it carries, and two tier fields: `record_trust_tier` is about the URL,
`request_trust_tier_ceiling` is the most a request carrying that `client_id` could reach.
The carried attestation is never verified there; follow up with `kya_lookup` on the
fingerprint, because the issuer is the source of truth and the document is not)
**Config / meta:** `config_discover_account`, `config_describe_context`,
`config_list_test_entities` (list the safe test subjects available to this session),
`trulioo_health`, `trulioo_capabilities`, `trulioo_getting_started`, `webhook_journal`
**Interactive UI (per account, SEP-1865):** `render_app_verdict` (host-rendered
`ui://` verdict card)
**Workflow prompts:** `kyc_onboarding_workflow`, `kyb_due_diligence_workflow`
(`prompts/list` is the authority on the set your connection offers)

### KYB workflow and responses

| Step | Tool | Use it when | Response and next step |
|---|---|---|---|
| 1 | `kyb_search` | You have a business name but not a confirmed registration identifier. | Returns `search_summary` with `candidate_count`, `has_results`, and `top_matches`. Confirm a candidate before verification. |
| As needed | `kyb_registration_lookup` | You need the accepted registration types or jurisdiction details for a country. | Returns supported registration-number types or jurisdictions of incorporation. Use them to format the next request. |
| As needed | `kyb_disambiguate_candidate` | Search returns multiple businesses. | Returns `resolved`, `candidate_index`, and the selected `candidate`, or numbered `options` for clients without interactive selection. Continue only after user confirmation. |
| 2 | `kyb_verify` | You have confirmed the business and are ready to verify it. | Returns `transaction_id`, `record_id`, `status`, `is_terminal`, and `next_action`. Branch on `status`; follow `next_action` when the result is not terminal. |
| While running | `kyb_get_partial_result` | Verification is still running and you need the latest state. | Returns the latest available fields plus `is_terminal` and `next_action`. Continue polling only while the result is not terminal. |
| 3 | `kyb_get_report` | Verification is complete and you need the final report. | Returns report JSON, or a PDF descriptor with `record_id`, `report_available`, `content_type`, and `resource_uri`. Store the report reference with the transaction. |
| Reference | `transaction_lookup` | You need to retrieve a previous verification from its transaction identifier. | Returns the matching record and identity context for a TransactionID or TransactionRecordID. |

### Shared verification result
```json
{ "transaction_id": "txn_...", "record_id": "rec_...",
  "status": "match",        // match | review | nomatch
  "is_terminal": true,      // false = poll kyb_get_partial_result
  "next_action": "none" }
```
- Errors are returned as typed results, not thrown.
- `is_terminal:false` means poll rather than assume completion.
- Per-upstream circuit breaker: one degraded provider does not take down the others.

## Know Your Agent (DAP)

Prove the real organization behind an agent and issue a signed, scoped, revocable
Digital Agent Profile that anyone can verify. It rides in the agent's A2A card.

1. **Verify the organization** - KYC/KYB + sanctions on the accountable owner (`/kya/principal/verify`).
2. **Register the agent** - the agent presents its own Ed25519 key; that key is the identity anchor. A build fingerprint binds the credential to the exact agent.
3. **Sign a scoped mandate** - `kya_issue_mandate` mints a short-lived, least-privilege attestation for named scopes (e.g. `purchase`), signed by `identity.trulioo.com`.
4. **Publish it** - the attestation attaches under `capabilities.extensions[]` (uri `https://identity.trulioo.com/a2a/kya/v1`) and is anchored in a tamper-evident log.

```json
{ "capabilities": { "extensions": [{
  "uri": "https://identity.trulioo.com/a2a/kya/v1",
  "params": {
    "attestation": "<compact JWS, alg=EdDSA>",
    "display": { "verified": true, "verified_as_of": "<ISO 8601>",
      "allowed_scopes": ["purchase"], "issuer": "https://identity.trulioo.com",
      "fingerprint": "<sha256 of the agent key + card>" }
  } }] } }
```
Scopes are signed and auditable by Trulioo but **enforced by the relying party** -
the attestation is a verifiable claim of trust, not an authorization grant on its own.

### Why agent trust has layers
1. **Identity anchor** - the agent holds its own key; everything binds to it.
2. **Integrity & provenance** - a build fingerprint binds the credential to the exact code; code change forces re-attestation.
3. **Authenticity of use** - a possession proof shows the presenter controls the anchor key.
4-5. **Origin & accountability** - KYC/KYB ties the agent to a real, sanctions-screened org; every issuance is anchored in a tamper-evident log.

### Verify an agent (DAP)
The critical path: confirm a profile + attestation before trusting an inbound agent.
- Integration path (recommended): call the MCP tool `kya_verify_agent` with the agent
  card - validates signature, fingerprint, expiry, sanctions; add `requested_action` to
  check the signed scope. The verification logic lives behind MCP, not a public write API.
- Public keyless primitives (issuer at identity.trulioo.com, no account):
  - `GET https://identity.trulioo.com/kya/attestation/{dap}` - look up standing by Digital Agent Profile.
  - `GET https://identity.trulioo.com/.well-known/jwks.json` - issuer signing keys to verify a JWS yourself.
```
# verify over MCP
curl -sN https://mcp.trulioo.com/mcp -H "Authorization: Bearer $TOKEN" -H "mcp-session-id: $SID" -H 'MCP-Protocol-Version: 2025-06-18' -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"kya_verify_agent","arguments":{"agent_card":{}}}}'
# public keyless lookup
curl -s https://identity.trulioo.com/kya/attestation/<dap>
curl -s https://identity.trulioo.com/.well-known/jwks.json
```
A verifier checks: JWS signature vs the pinned issuer key by kid (alg from key, never the
token header); fingerprint vs agent key + card; exp; `verified`; `assurance.trust_tier`
against your own minimum; and the signed allowed_scopes. Scopes are enforced by the
relying party.

No standing sanctions boolean is signed. Signing one would assert a check that may not
have run, on a payment-adjacent credential. Screening is an optional provenanced claim
carried only when it actually ran, and a hit or review suspends the credential's standing
through its `status_reference` - so check standing, not a boolean.

## Orchestration patterns
- **Agent as orchestrator** - one agent connects once and calls verification tools mid-task, branching on the typed result.
- **Enterprise agent teams** - a fleet shares one MCP connection and one identity layer; each agent carries its own DAP so downstream systems know which accountable org acted.

## Security & honest limits
- Schema-enforced inputs (`additionalProperties: false`) - malformed/injected calls rejected before any upstream.
- Prompt-injection resistance - tools act only on typed arguments; document/field text is data, never an instruction.
- Algorithm pinning - attestation verification pins the alg from the resolved key (EdDSA/ES256 only); `alg:none` and alg-confusion rejected.
- Honest limits - a DAP proves identity, integrity, scope and accountability; it does not authorize a payment or guarantee behavior. Scope enforcement is the relying party's.

## Changelog
- **2026-09-02** - Added the Authentication section: the 401 + `WWW-Authenticate` discovery
  entry point, the full OAuth 2.1 + PKCE handshake with worked curl for /register, /authorize
  and /token, sandbox mechanics (`VerificationType: Demo`, the `test_mode` annotation and its
  live-data case), session lifetimes, and scopes. Corrected the tool-call examples, which
  omitted the required bearer token.
- **2026-08-03** - Launched the unified /developer surface (consolidates the former
  MCP + KYA docs); added machine twins (index.md, llms.txt, index.json); documented
  the full tool set, the KYA/DAP flow, and the Claude Desktop -> production-scale
  path; canonical endpoint `https://mcp.trulioo.com/mcp`; public setup for Claude Code,
  OpenAI Codex, and the ChatGPT connector.
- **2026-08-02** - KYA A2A card carries the Trulioo-signed attestation (JWS) under
  capabilities.extensions[]; agent's own key is the identity anchor; public DAP
  verification via issuer JWKS at identity.trulioo.com/.well-known/jwks.json.
- **2026-08-01** - MCP hosted endpoint with self-registering OAuth; typed tools across
  KYC/KYB/AML/DocV/age/monitoring; approved live access uses OAuth 2.1.

## Links
- Developer home (HTML): https://mcp.trulioo.com/developer/
- Verify demo (live agent-card console): https://mcp.trulioo.com/kya
- KYA extension schema: https://identity.trulioo.com/a2a/kya/v1/schema.json
- Card example: https://identity.trulioo.com/a2a/kya/v1/card.example.json
