Scopes & role permissions
The hosted connector requests two coarse scopes —didit:management (workspace operations) and didit:verification (running checks). Inside those, your console role is the real boundary, enforced server-side on every call:
| Role | Can do |
|---|---|
OWNER / ADMIN | Everything the scopes allow (reads, writes, deletes, billing). |
COMPLIANCE_OFFICER | Reads + compliance writes (reviews, cases, lists). |
DEVELOPER | Developer resources (workflows, webhooks, API keys, sessions). |
READER | Reads only — writes return 403. |
403 on one tool means your role lacks that permission, not that the whole connection failed.
Cross-app & org resolution
The Didit management API is scoped per application (/organization/{org}/application/{app}/…). The MCP resolves scope for you:
- Single org + app → resolved automatically; you never pass IDs.
- Multiple apps → the cross-app tools (
didit_session_search,didit_transaction_search,didit_case_search,didit_vendor_user_search,didit_vendor_business_search,didit_analytics) aggregate over all of them in one call, each row tagged with itsorganization_id/application_id. - The per-app list tools (
didit_session_list,didit_transaction_list, …) auto-span every app when you don’t specify one, so a plain “list my sessions” works even with many apps. Passorganization_id+application_idto narrow to a single app.
didit_context_get to see all your organizations and applications (and their IDs) in a single call.
Branching workflows
Simple workflows are a flat list of features. For conditional logic — decline on a status, route on an extracted field, gate a step on a match, add a Document-AI request — the MCP drives Didit’s node graph (the same structure as the console’s visual builder). A graph is{ start_node, nodes }; each node is a feature, branch, status (terminal), action, or webhook. Branch rules support operators including fuzzy_match (string fields, with a score 0–100 similarity threshold) on extracted fields like kyc.extra_fields.profession, and a DOCUMENT_AI feature node requests documents (e.g. proof of funds).
Editing an existing workflow (recommended)
Real workflows carry large feature configs — an OCRdocuments_allowed allow-list can be 150 KB+, plus POA lists and phone-country lists. So don’t read the whole graph and resend it. Instead edit with small operations:
Locate & inspect
didit_workflow_search { workflow_id } finds it across all your apps. didit_workflow_get_graph returns the structure with big configs summarized (it never overflows); didit_workflow_get_field_definitions lists branchable fields + valid operators.Edit with ops
didit_workflow_edit_graph takes a few small ops — set_next, set_node, set_branches, merge_node_config — and the MCP fetches the full graph server-side, applies them, validates, auto-creates a draft, and saves. Your allow-lists are preserved byte-for-byte and never pass through the request. A validation failure returns applied: false (nothing saved).next to a new branch, and add the branch + a terminal Declined node + a DOCUMENT_AI step (whose next points back at the original next step). The other 10 nodes — and their allow-lists — are untouched.
“After ID verification, if it was declined, decline; if profession ≈ ‘Software Engineer’ (≥80%), request proof of funds; then continue.” → onebranchnode:kyc.status == Declined→ aDeclinedstatus node;kyc.extra_fields.professionfuzzy_match“Software Engineer” score 80 → aDOCUMENT_AIproof-of-funds node → and an else path back into the pipeline.
A branch always needs an explicit else. The catch-all (the path taken when no condition matches) must be a branch with empty
rules — e.g. { "id": "else", "rules": [], "goto": "<liveness>" } — not the branch node’s bare next. The MCP (and the backend) auto-convert a branch node’s next into an else branch, so a branch never renders as an ambiguous edge with no visible else.didit_workflow_validate_graph then didit_workflow_set_graph (full graph). Even a plain didit_workflow_create is saved as this same node graph — the MCP assembles your features list into chained feature nodes ending in an auto-decide status, so the features actually run (it publishes by default; pass status: "draft" to keep it unpublished). See Create Workflow → branching & node-based workflows for the full JSON shape.
Feature configs and allow-lists
A feature node’sconfig restricts what it accepts. The canonical OCR allow-list is per-country → per-document → object: { "<ISO3>": { "<CODE>": { "enabled": 1 } } } (e.g. { "ESP": { "P": { "enabled": 1 } } }). The MCP normalizes friendlier shapes for you before validating, so you don’t have to hand-write that structure:
| You can pass | Normalized to |
|---|---|
A doc-type array — ["PASSPORT"] | that document for every country (passport-only worldwide) |
Document names or codes — PASSPORT/P, NATIONAL_ID/ID, DRIVER_LICENSE/DL, RESIDENCE_PERMIT/RP (also SSC, HIC, TC) | the canonical code |
An "ALL" country key — { "ALL": { "P": 1 } } | applied to every country |
Scalar flags — { "ESP": { "P": 1 } } | { "ESP": { "P": { "enabled": 1 } } } |
| Omitted entirely | all documents, all countries |
PROOF_OF_ADDRESS’s poa_languages_allowed is the same idea — pass a language array (["es"]) or a map ({ "es": 1 }). These conveniences apply wherever you give the MCP a feature config: didit_workflow_create features, and the node configs in set_graph / edit_graph / validate_graph.
Response shape (V3 contract)
Every tool returns the verbatim JSON of the underlying REST endpoint. Two things to know:- Synchronous tools (workflows, billing, standalone
verify_*, list management) return their final result inline — act on it the same turn. - Session creation is asynchronous —
didit_session_createreturns aurlthe user must complete; the decision arrives by webhook ordidit_session_get_decision.
node_id per entry (id_verifications[], liveness_checks[], face_matches[], aml_screenings[], poa_verifications[], phone_verifications[], email_verifications[], database_validations[], reviews[], plus KYB registry_checks[], document_verifications[], key_people_checks[]). See Data models and Verification statuses.
Handling webhooks
Let a webhook deliver the final decision to your backend rather than polling from the agent:- The agent calls
didit_webhook_createonce with your public HTTPS endpoint,webhook_version: "v3", andsubscribed_events(typically["status.updated", "data.updated"]). The response includes asecret_shared_key— store it asDIDIT_WEBHOOK_SECRET. - Your backend verifies the canonical X-Signature-V2 header on every delivery:
shortenFloats→sortKeys→JSON.stringify(unescaped unicode) → HMAC-SHA256 →timingSafeEqual. Reject ifabs(now − X-Timestamp) > 300seconds. - Deduplicate on
event_id. Return2xxwithin 5 seconds; move heavy work to a queue. - Didit retries on
5xx/404up to 2 times (~1 min, then ~4 min), then drops the delivery — replay failed ones from the Deliveries tab in the console.
Troubleshooting
The agent calls a tool and gets a 403
The agent calls a tool and gets a 403
Re-authenticate: reconnect / re-run Log in with Didit. If only one tool 403s, your role lacks that permission (e.g. a
READER attempting a write). (Note: the MCP is Bearer/OAuth only — an x-api-key is rejected by every tool, so an API key won’t fix a 403.)A write or delete didn't run
A write or delete didn't run
Destructive tools require confirmation in your client. Approve the call, or check that the tool’s confirmation prompt isn’t being auto-denied.
didit_org_reveal_application_api_key additionally needs confirm: true.'organization_id is required' or no results
'organization_id is required' or no results
You likely have multiple apps. Use a
*_search tool (which spans everything) or run didit_context_get and pass the organization_id / application_id you want.Browser didn't open for sign-in (hosted)
Browser didn't open for sign-in (hosted)
Some clients (Windsurf, Zed) connect through the
mcp-remote bridge — make sure npx -y mcp-remote@latest https://mcp.didit.me/mcp is in the config. Re-open the client to retrigger the OAuth handshake.Tools don't appear in the client
Tools don't appear in the client
Confirm the server connected (most clients show a status dot) and that you completed Log in with Didit. Restart the MCP host after editing its config.
Security
The MCP acts with your real permissions and can mutate live resources. Keep “review tool calls before running” enabled in your client — especially for Write and Destructive tools (deletes,org_top_up, org_reveal_application_api_key). The MCP itself stores no secret in your config (it uses Log in with Didit); and if a tool returns an application API key (a REST credential), don’t paste it into a shared chat.
Self-hosting
The server is open source (MIT) —@didit-protocol/mcp-server / github.com/didit-protocol/mcp. It always authenticates as a Didit user (there is no API-key mode); run it in either transport:
verification.didit.me, apx.didit.me, business.didit.me) — override them for a private deployment. See the repository’s README and ARCHITECTURE for the full environment-variable reference and the resource-server / authorization-server split.
Tools reference
Every tool, grouped by area.
Examples
Prompts and end-to-end conversations.